+ Magento supports PHP 7.1.3 or later. Please read
+
Magento System Requirements .
HTML;
diff --git a/app/code/Magento/AdminNotification/Block/Window.php b/app/code/Magento/AdminNotification/Block/Window.php
index 9563626ee2577..e9b4bfa44893d 100644
--- a/app/code/Magento/AdminNotification/Block/Window.php
+++ b/app/code/Magento/AdminNotification/Block/Window.php
@@ -8,6 +8,8 @@
namespace Magento\AdminNotification\Block;
/**
+ * Admin notification window block
+ *
* @api
* @since 100.0.2
*/
@@ -40,7 +42,7 @@ class Window extends \Magento\Backend\Block\Template
protected $_criticalCollection;
/**
- * @var \Magento\Adminnotification\Model\Inbox
+ * @var \Magento\AdminNotification\Model\Inbox
*/
protected $_latestItem;
@@ -92,7 +94,7 @@ protected function _toHtml()
/**
* Retrieve latest critical item
*
- * @return bool|\Magento\Adminnotification\Model\Inbox
+ * @return bool|\Magento\AdminNotification\Model\Inbox
*/
protected function _getLatestItem()
{
diff --git a/app/code/Magento/AdminNotification/etc/db_schema.xml b/app/code/Magento/AdminNotification/etc/db_schema.xml
index 35e6045b607d1..29d928ced2084 100644
--- a/app/code/Magento/AdminNotification/etc/db_schema.xml
+++ b/app/code/Magento/AdminNotification/etc/db_schema.xml
@@ -21,16 +21,16 @@
default="0" comment="Flag if notification read"/>
-
+
-
+
-
+
-
+
@@ -40,7 +40,7 @@
default="0" comment="Problem type"/>
-
+
diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php
index b46e286e75007..d78c4f5e61af3 100644
--- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php
+++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php
@@ -103,13 +103,13 @@ public function testGetAllWebsitesValue()
$this->webSiteModel->expects($this->once())->method('getBaseCurrency')->willReturn($currency);
$expectedResult = AdvancedPricing::VALUE_ALL_WEBSITES . ' [' . $currencyCode . ']';
- $this->websiteString = $this->getMockBuilder(
+ $websiteString = $this->getMockBuilder(
\Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\Website::class
)
->setMethods(['_clearMessages', '_addMessages'])
->setConstructorArgs([$this->storeResolver, $this->webSiteModel])
->getMock();
- $result = $this->websiteString->getAllWebsitesValue();
+ $result = $websiteString->getAllWebsitesValue();
$this->assertEquals($expectedResult, $result);
}
diff --git a/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php b/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php
index 546983bb5e5a8..c0c224766eb3c 100644
--- a/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php
+++ b/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php
@@ -10,6 +10,9 @@
use Magento\Search\Model\QueryInterface;
use Magento\AdvancedSearch\Model\SuggestedQueriesInterface;
+/**
+ * Class DataProvider
+ */
class DataProvider implements SuggestedQueriesInterface
{
/**
@@ -51,6 +54,8 @@ class DataProvider implements SuggestedQueriesInterface
private $recommendationsFactory;
/**
+ * DataProvider constructor.
+ *
* @param ScopeConfigInterface $scopeConfig
* @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
* @param \Magento\AdvancedSearch\Model\ResourceModel\RecommendationsFactory $recommendationsFactory
@@ -69,18 +74,20 @@ public function __construct(
}
/**
+ * Is Results Count Enabled
+ *
* @return bool
*/
public function isResultsCountEnabled()
{
- return (bool)$this->scopeConfig->getValue(
+ return $this->scopeConfig->isSetFlag(
self::CONFIG_RESULTS_COUNT_ENABLED,
ScopeInterface::SCOPE_STORE
);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getItems(QueryInterface $query)
{
@@ -102,6 +109,8 @@ public function getItems(QueryInterface $query)
}
/**
+ * Return Search Recommendations
+ *
* @param QueryInterface $query
* @return array
*/
@@ -126,17 +135,21 @@ private function getSearchRecommendations(\Magento\Search\Model\QueryInterface $
}
/**
+ * Is Search Recommendations Enabled
+ *
* @return bool
*/
private function isSearchRecommendationsEnabled()
{
- return (bool)$this->scopeConfig->getValue(
+ return $this->scopeConfig->isSetFlag(
self::CONFIG_IS_ENABLED,
ScopeInterface::SCOPE_STORE
);
}
/**
+ * Return Search Recommendations Count
+ *
* @return int
*/
private function getSearchRecommendationsCount()
diff --git a/app/code/Magento/AdvancedSearch/etc/db_schema.xml b/app/code/Magento/AdvancedSearch/etc/db_schema.xml
index 9fae40411098c..2dd8c68e2d5fd 100644
--- a/app/code/Magento/AdvancedSearch/etc/db_schema.xml
+++ b/app/code/Magento/AdvancedSearch/etc/db_schema.xml
@@ -14,13 +14,13 @@
default="0" comment="Query Id"/>
-
+
-
-
diff --git a/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml b/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml
index f6e5b955816e2..83f27def4b4e8 100644
--- a/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml
+++ b/app/code/Magento/Analytics/Test/Mftf/Data/UserData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
noreport
No
@@ -18,7 +18,7 @@
en_US
true
123123q
-
+
restrictedWebUser
restricted
diff --git a/app/code/Magento/Analytics/Test/Mftf/Page/AdminConfigPage.xml b/app/code/Magento/Analytics/Test/Mftf/Page/AdminConfigPage.xml
new file mode 100644
index 0000000000000..c4ced12e67e07
--- /dev/null
+++ b/app/code/Magento/Analytics/Test/Mftf/Page/AdminConfigPage.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigAdvancedReportingSection.xml b/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigAdvancedReportingSection.xml
new file mode 100644
index 0000000000000..2e5f2b762a7b1
--- /dev/null
+++ b/app/code/Magento/Analytics/Test/Mftf/Section/AdminConfigAdvancedReportingSection.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml
index ff89ca9b663ee..914cb59b64e4e 100644
--- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml
+++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml
@@ -18,15 +18,14 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml
index d9617209dcdff..1c1a3b27b06af 100644
--- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml
+++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml
@@ -16,32 +16,26 @@
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml
index 2d5594a43b1a7..bb682c4468012 100644
--- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml
+++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml
@@ -17,18 +17,14 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml
index d8aed1250d82a..58e809ec45c4a 100644
--- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml
+++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationPermissionTest.xml
@@ -17,49 +17,49 @@
-
-
-
-
-
+
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml
index 3f17df108b50b..58e62500b8203 100644
--- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml
+++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml
@@ -19,17 +19,16 @@
-
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Analytics/etc/adminhtml/system.xml b/app/code/Magento/Analytics/etc/adminhtml/system.xml
index 4e21648d00ce8..c7da840b7e665 100644
--- a/app/code/Magento/Analytics/etc/adminhtml/system.xml
+++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml
@@ -36,6 +36,9 @@
Magento\Analytics\Model\Config\Source\Vertical
Magento\Analytics\Model\Config\Backend\Vertical
Magento\Analytics\Block\Adminhtml\System\Config\Vertical
+
+ 1
+
Enabled Template Path Hints for Admin
@@ -338,15 +338,26 @@
Images Upload Configuration
-
+
+ Enable Frontend Resize
+ Magento\Config\Model\Config\Source\Yesno
+ Resize performed via javascript before file upload.
+
+
Maximum Width
validate-greater-than-zero validate-number required-entry
Maximum allowed width for uploaded image.
+
+ 1
+
-
+
Maximum Height
validate-greater-than-zero validate-number required-entry
Maximum allowed height for uploaded image.
+
+ 1
+
diff --git a/app/code/Magento/Backend/etc/config.xml b/app/code/Magento/Backend/etc/config.xml
index 45d283ad3ff22..8283fa18dd370 100644
--- a/app/code/Magento/Backend/etc/config.xml
+++ b/app/code/Magento/Backend/etc/config.xml
@@ -29,6 +29,7 @@
1
+ 1
1920
1200
diff --git a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml
index 966372773f295..4d9ba6a8c4bad 100644
--- a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml
+++ b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml
@@ -13,8 +13,9 @@
data-mage-init='{
"Magento_Backend/js/media-uploader" : {
"maxFileSize": = /* @escapeNotVerified */ $block->getFileSizeService()->getMaxFileSize() ?>,
- "maxWidth":= /* @escapeNotVerified */ $block->getImageUploadMaxWidth() ?> ,
- "maxHeight": = /* @escapeNotVerified */ $block->getImageUploadMaxHeight() ?>
+ "maxWidth": = /* @escapeNotVerified */ $block->getImageUploadMaxWidth() ?>,
+ "maxHeight": = /* @escapeNotVerified */ $block->getImageUploadMaxHeight() ?>,
+ "isResizeEnabled": = /* @noEscape */ $block->getImageUploadConfigData()->getIsResizeEnabled() ?>
}
}'
>
diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js
index 7e0b6bdfb46dd..119e7a35747cb 100644
--- a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js
+++ b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js
@@ -33,9 +33,20 @@ define([
* @private
*/
_create: function () {
- var
- self = this,
- progressTmpl = mageTemplate('[data-template="uploader"]');
+ var self = this,
+ progressTmpl = mageTemplate('[data-template="uploader"]'),
+ isResizeEnabled = this.options.isResizeEnabled,
+ resizeConfiguration = {
+ action: 'resize',
+ maxWidth: this.options.maxWidth,
+ maxHeight: this.options.maxHeight
+ };
+
+ if (!isResizeEnabled) {
+ resizeConfiguration = {
+ action: 'resize'
+ };
+ }
this.element.find('input[type=file]').fileupload({
dataType: 'json',
@@ -52,8 +63,7 @@ define([
* @param {Object} data
*/
add: function (e, data) {
- var
- fileSize,
+ var fileSize,
tmpl;
$.each(data.files, function (index, file) {
@@ -123,11 +133,10 @@ define([
this.element.find('input[type=file]').fileupload('option', {
process: [{
action: 'load',
- fileTypes: /^image\/(gif|jpeg|png)$/,
- maxFileSize: this.options.maxFileSize
- }, {
- action: 'resize'
- }, {
+ fileTypes: /^image\/(gif|jpeg|png)$/
+ },
+ resizeConfiguration,
+ {
action: 'save'
}]
});
diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js b/app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js
index 0a692a9b868cc..c2a0d4dab1fb3 100644
--- a/app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js
+++ b/app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js
@@ -67,6 +67,7 @@ define([
* 'Confirm' action handler.
*/
confirm: function () {
+ $('body').trigger('processStart');
dataPost().postData(requestData);
}
}
diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index.php b/app/code/Magento/Backup/Controller/Adminhtml/Index.php
index dcafbc7370d2d..0edeb5565f288 100644
--- a/app/code/Magento/Backup/Controller/Adminhtml/Index.php
+++ b/app/code/Magento/Backup/Controller/Adminhtml/Index.php
@@ -5,21 +5,27 @@
*/
namespace Magento\Backup\Controller\Adminhtml;
+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
*
* @author Magento Core Team
* @api
* @since 100.0.2
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
*/
-abstract class Index extends \Magento\Backend\App\Action
+abstract class Index extends Action implements HttpGetActionInterface
{
/**
* Authorization level of a basic admin session
*
* @see _isAllowed()
*/
- const ADMIN_RESOURCE = 'Magento_Backend::backup';
+ const ADMIN_RESOURCE = 'Magento_Backup::backup';
/**
* Core registry
@@ -48,6 +54,11 @@ abstract class Index extends \Magento\Backend\App\Action
*/
protected $maintenanceMode;
+ /**
+ * @var Helper
+ */
+ private $helper;
+
/**
* @param \Magento\Backend\App\Action\Context $context
* @param \Magento\Framework\Registry $coreRegistry
@@ -55,6 +66,7 @@ abstract class Index extends \Magento\Backend\App\Action
* @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,
@@ -62,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 0000000000000..f6fe430ae0838
--- /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 750262ab1c14a..9502377a39d06 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 b0bc292ffe926..c6df6a7366852 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 8fbd5da1c9842..bc458a0a8e4bf 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 496bb79343092..26f8817c0a1bb 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 b7dfb30c0a1b3..56a7ef42a0bc2 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 0cab5f0ad1e99..0000000000000
--- 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 4028452d04439..90f6fa861b40f 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 0000000000000..fb0808983b9c8
--- /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 0000000000000..3470f528e5ceb
--- /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 0000000000000..a5308dce5cc52
--- /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/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php
index ca252aabe54a9..14ec829d98024 100644
--- a/app/code/Magento/Braintree/Controller/Paypal/Review.php
+++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php
@@ -12,11 +12,12 @@
use Magento\Braintree\Gateway\Config\PayPal\Config;
use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\App\Action\HttpPostActionInterface;
/**
* Class Review
*/
-class Review extends AbstractAction
+class Review extends AbstractAction implements HttpPostActionInterface
{
/**
* @var QuoteUpdater
@@ -60,7 +61,7 @@ public function execute()
try {
$this->validateQuote($quote);
- if ($this->validateRequestData($requestData)) {
+ if ($requestData && $this->validateRequestData($requestData)) {
$this->quoteUpdater->execute(
$requestData['nonce'],
$requestData['details'],
@@ -91,6 +92,8 @@ public function execute()
}
/**
+ * Validate request data
+ *
* @param array $requestData
* @return boolean
*/
diff --git a/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php
index a035c84b4cafd..4d63ee4125b74 100644
--- a/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php
+++ b/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php
@@ -49,6 +49,8 @@ public function build(array $buildSubject)
$payment = $paymentDO->getPayment();
$data = $payment->getAdditionalInformation();
+ // the payment token could be stored only if a customer checks the Vault flow on storefront
+ // see https://developers.braintreepayments.com/guides/paypal/vault/javascript/v2#invoking-the-vault-flow
if (!empty($data[VaultConfigProvider::IS_ACTIVE_CODE])) {
$result[self::$optionsKey] = [
self::$storeInVaultOnSuccess => true
diff --git a/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php
index 4280663178efb..950634ba2d9e2 100644
--- a/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php
+++ b/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php
@@ -6,6 +6,8 @@
namespace Magento\Braintree\Gateway\Request;
use Magento\Braintree\Gateway\SubjectReader;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Payment\Gateway\Command\CommandException;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Magento\Payment\Helper\Formatter;
@@ -41,6 +43,9 @@ public function build(array $buildSubject)
$payment = $paymentDO->getPayment();
$extensionAttributes = $payment->getExtensionAttributes();
$paymentToken = $extensionAttributes->getVaultPaymentToken();
+ if ($paymentToken === null) {
+ throw new CommandException(__('The Payment Token is not available to perform the request.'));
+ }
return [
'amount' => $this->formatPrice($this->subjectReader->readAmount($buildSubject)),
'paymentMethodToken' => $paymentToken->getGatewayToken()
diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
index fe5895541543d..aa23fa767d1ed 100644
--- a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
+++ b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
@@ -148,7 +148,7 @@ private function updateBillingAddress(Quote $quote, array $details)
{
$billingAddress = $quote->getBillingAddress();
- if ($this->config->isRequiredBillingAddress()) {
+ if ($this->config->isRequiredBillingAddress() && !empty($details['billingAddress'])) {
$this->updateAddressData($billingAddress, $details['billingAddress']);
} else {
$this->updateAddressData($billingAddress, $details['shippingAddress']);
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 0000000000000..c2e6af9dcd735
--- /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 27e2039fe526e..9eaae8b33e73f 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 ee7158c2b63f7..17d634c009b3e 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/Data/BraintreeData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml
index 8f2588a6effa5..f00e3fa286b08 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/Section/AdminMenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml
index 660c7393b4061..eb7a9ce2c376e 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/Section/BraintreeConfigurationPaymentSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml
index d8e1b837a1d1b..d9f2b14a40e2f 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml
@@ -10,6 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml
index 7f6d862ada085..2b5d60bea24e1 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml
@@ -61,10 +61,12 @@
+
-
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml
index df2e98816f0d3..2ddefa40b536c 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 0000000000000..d1d685effd133
--- /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/Gateway/Request/VaultCaptureDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php
index 25ccd8b32d10e..d4e1f2745e3f3 100644
--- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php
@@ -3,10 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Braintree\Test\Unit\Gateway\Request;
-use Magento\Braintree\Gateway\SubjectReader;
use Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder;
+use Magento\Braintree\Gateway\SubjectReader;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Magento\Sales\Api\Data\OrderPaymentExtension;
use Magento\Sales\Model\Order\Payment;
@@ -26,47 +28,46 @@ class VaultCaptureDataBuilderTest extends \PHPUnit\Framework\TestCase
/**
* @var PaymentDataObjectInterface|MockObject
*/
- private $paymentDOMock;
+ private $paymentDO;
/**
* @var Payment|MockObject
*/
- private $paymentMock;
+ private $payment;
/**
- * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject
+ * @var SubjectReader|MockObject
*/
- private $subjectReaderMock;
+ private $subjectReader;
/**
* @inheritdoc
*/
- protected function setUp()
+ protected function setUp(): void
{
- $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class);
- $this->paymentMock = $this->getMockBuilder(Payment::class)
+ $this->paymentDO = $this->createMock(PaymentDataObjectInterface::class);
+ $this->payment = $this->getMockBuilder(Payment::class)
->disableOriginalConstructor()
->getMock();
- $this->paymentDOMock->expects(static::once())
- ->method('getPayment')
- ->willReturn($this->paymentMock);
+ $this->paymentDO->method('getPayment')
+ ->willReturn($this->payment);
- $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
+ $this->subjectReader = $this->getMockBuilder(SubjectReader::class)
->disableOriginalConstructor()
->getMock();
- $this->builder = new VaultCaptureDataBuilder($this->subjectReaderMock);
+ $this->builder = new VaultCaptureDataBuilder($this->subjectReader);
}
/**
- * \Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder::build
+ * Checks the result after builder execution.
*/
- public function testBuild()
+ public function testBuild(): void
{
$amount = 30.00;
$token = '5tfm4c';
$buildSubject = [
- 'payment' => $this->paymentDOMock,
+ 'payment' => $this->paymentDO,
'amount' => $amount,
];
@@ -75,36 +76,68 @@ public function testBuild()
'paymentMethodToken' => $token,
];
- $this->subjectReaderMock->expects(self::once())
- ->method('readPayment')
+ $this->subjectReader->method('readPayment')
->with($buildSubject)
- ->willReturn($this->paymentDOMock);
- $this->subjectReaderMock->expects(self::once())
- ->method('readAmount')
+ ->willReturn($this->paymentDO);
+ $this->subjectReader->method('readAmount')
->with($buildSubject)
->willReturn($amount);
- $paymentExtensionMock = $this->getMockBuilder(OrderPaymentExtension::class)
+ /** @var OrderPaymentExtension|MockObject $paymentExtension */
+ $paymentExtension = $this->getMockBuilder(OrderPaymentExtension::class)
->setMethods(['getVaultPaymentToken'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $paymentTokenMock = $this->getMockBuilder(PaymentToken::class)
+ /** @var PaymentToken|MockObject $paymentToken */
+ $paymentToken = $this->getMockBuilder(PaymentToken::class)
->disableOriginalConstructor()
->getMock();
- $paymentExtensionMock->expects(static::once())
- ->method('getVaultPaymentToken')
- ->willReturn($paymentTokenMock);
- $this->paymentMock->expects(static::once())
- ->method('getExtensionAttributes')
- ->willReturn($paymentExtensionMock);
+ $paymentExtension->method('getVaultPaymentToken')
+ ->willReturn($paymentToken);
+ $this->payment->method('getExtensionAttributes')
+ ->willReturn($paymentExtension);
- $paymentTokenMock->expects(static::once())
- ->method('getGatewayToken')
+ $paymentToken->method('getGatewayToken')
->willReturn($token);
$result = $this->builder->build($buildSubject);
self::assertEquals($expected, $result);
}
+
+ /**
+ * Checks a builder execution if Payment Token doesn't exist.
+ *
+ * @expectedException \Magento\Payment\Gateway\Command\CommandException
+ * @expectedExceptionMessage The Payment Token is not available to perform the request.
+ */
+ public function testBuildWithoutPaymentToken(): void
+ {
+ $amount = 30.00;
+ $buildSubject = [
+ 'payment' => $this->paymentDO,
+ 'amount' => $amount,
+ ];
+
+ $this->subjectReader->method('readPayment')
+ ->with($buildSubject)
+ ->willReturn($this->paymentDO);
+ $this->subjectReader->method('readAmount')
+ ->with($buildSubject)
+ ->willReturn($amount);
+
+ /** @var OrderPaymentExtension|MockObject $paymentExtension */
+ $paymentExtension = $this->getMockBuilder(OrderPaymentExtension::class)
+ ->setMethods(['getVaultPaymentToken'])
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->payment->method('getExtensionAttributes')
+ ->willReturn($paymentExtension);
+ $paymentExtension->method('getVaultPaymentToken')
+ ->willReturn(null);
+
+ $this->builder->build($buildSubject);
+ }
}
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php
index 62452228b6186..a2b5380d2884b 100644
--- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php
@@ -3,23 +3,24 @@
* 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\Quote\Model\Quote;
-use Magento\Quote\Model\Quote\Address;
-use Magento\Quote\Model\Quote\Payment;
-use Magento\Quote\Api\CartRepositoryInterface;
-use Magento\Braintree\Model\Ui\PayPal\ConfigProvider;
-use Magento\Braintree\Observer\DataAssignObserver;
use Magento\Braintree\Gateway\Config\PayPal\Config;
use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater;
+use Magento\Braintree\Model\Ui\PayPal\ConfigProvider;
+use Magento\Braintree\Observer\DataAssignObserver;
+use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Api\Data\CartExtensionInterface;
+use Magento\Quote\Model\Quote;
+use Magento\Quote\Model\Quote\Address;
+use Magento\Quote\Model\Quote\Payment;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
/**
* Class QuoteUpdaterTest
*
- * @see \Magento\Braintree\Model\Paypal\Helper\QuoteUpdater
- *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase
@@ -27,24 +28,24 @@ class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase
const TEST_NONCE = '3ede7045-2aea-463e-9754-cd658ffeeb48';
/**
- * @var Config|\PHPUnit_Framework_MockObject_MockObject
+ * @var Config|MockObject
*/
- private $configMock;
+ private $config;
/**
- * @var CartRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var CartRepositoryInterface|MockObject
*/
- private $quoteRepositoryMock;
+ private $quoteRepository;
/**
- * @var Address|\PHPUnit_Framework_MockObject_MockObject
+ * @var Address|MockObject
*/
- private $billingAddressMock;
+ private $billingAddress;
/**
- * @var Address|\PHPUnit_Framework_MockObject_MockObject
+ * @var Address|MockObject
*/
- private $shippingAddressMock;
+ private $shippingAddress;
/**
* @var QuoteUpdater
@@ -52,17 +53,17 @@ class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase
private $quoteUpdater;
/**
- * @return void
+ * @inheritdoc
*/
protected function setUp()
{
- $this->configMock = $this->getMockBuilder(Config::class)
+ $this->config = $this->getMockBuilder(Config::class)
->disableOriginalConstructor()
->getMock();
- $this->quoteRepositoryMock = $this->getMockBuilder(CartRepositoryInterface::class)
+ $this->quoteRepository = $this->getMockBuilder(CartRepositoryInterface::class)
->getMockForAbstractClass();
- $this->billingAddressMock = $this->getMockBuilder(Address::class)
+ $this->billingAddress = $this->getMockBuilder(Address::class)
->setMethods(
[
'setLastname',
@@ -77,9 +78,10 @@ protected function setUp()
'setShouldIgnoreValidation',
'getEmail'
]
- )->disableOriginalConstructor()
+ )
+ ->disableOriginalConstructor()
->getMock();
- $this->shippingAddressMock = $this->getMockBuilder(Address::class)
+ $this->shippingAddress = $this->getMockBuilder(Address::class)
->setMethods(
[
'setLastname',
@@ -93,61 +95,61 @@ protected function setUp()
'setPostcode',
'setShouldIgnoreValidation'
]
- )->disableOriginalConstructor()
+ )
+ ->disableOriginalConstructor()
->getMock();
$this->quoteUpdater = new QuoteUpdater(
- $this->configMock,
- $this->quoteRepositoryMock
+ $this->config,
+ $this->quoteRepository
);
}
/**
- * @return void
+ * Checks if quote details can be update by the response from Braintree.
+ *
* @throws \Magento\Framework\Exception\LocalizedException
*/
- public function testExecute()
+ public function testExecute(): void
{
$details = $this->getDetails();
- $quoteMock = $this->getQuoteMock();
- $paymentMock = $this->getPaymentMock();
+ $quote = $this->getQuoteMock();
+ $payment = $this->getPaymentMock();
- $quoteMock->expects(self::once())
- ->method('getPayment')
- ->willReturn($paymentMock);
+ $quote->method('getPayment')
+ ->willReturn($payment);
- $paymentMock->expects(self::once())
- ->method('setMethod')
+ $payment->method('setMethod')
->with(ConfigProvider::PAYPAL_CODE);
- $paymentMock->expects(self::once())
- ->method('setAdditionalInformation')
+ $payment->method('setAdditionalInformation')
->with(DataAssignObserver::PAYMENT_METHOD_NONCE, self::TEST_NONCE);
- $this->updateQuoteStep($quoteMock, $details);
+ $this->updateQuoteStep($quote, $details);
- $this->quoteUpdater->execute(self::TEST_NONCE, $details, $quoteMock);
+ $this->quoteUpdater->execute(self::TEST_NONCE, $details, $quote);
}
/**
+ * Disables quote's addresses validation.
+ *
* @return void
*/
- private function disabledQuoteAddressValidationStep()
+ private function disabledQuoteAddressValidationStep(): void
{
- $this->billingAddressMock->expects(self::once())
- ->method('setShouldIgnoreValidation')
+ $this->billingAddress->method('setShouldIgnoreValidation')
->with(true);
- $this->shippingAddressMock->expects(self::once())
- ->method('setShouldIgnoreValidation')
+ $this->shippingAddress->method('setShouldIgnoreValidation')
->with(true);
- $this->billingAddressMock->expects(self::once())
- ->method('getEmail')
+ $this->billingAddress->method('getEmail')
->willReturn('bt_buyer_us@paypal.com');
}
/**
+ * Gets quote's details.
+ *
* @return array
*/
- private function getDetails()
+ private function getDetails(): array
{
return [
'email' => 'bt_buyer_us@paypal.com',
@@ -177,54 +179,51 @@ private function getDetails()
}
/**
+ * Updates shipping address details.
+ *
* @param array $details
*/
- private function updateShippingAddressStep(array $details)
+ private function updateShippingAddressStep(array $details): void
{
- $this->shippingAddressMock->expects(self::once())
- ->method('setLastname')
+ $this->shippingAddress->method('setLastname')
->with($details['lastName']);
- $this->shippingAddressMock->expects(self::once())
- ->method('setFirstname')
+ $this->shippingAddress->method('setFirstname')
->with($details['firstName']);
- $this->shippingAddressMock->expects(self::once())
- ->method('setEmail')
+ $this->shippingAddress->method('setEmail')
->with($details['email']);
- $this->shippingAddressMock->expects(self::once())
- ->method('setCollectShippingRates')
+ $this->shippingAddress->method('setCollectShippingRates')
->with(true);
- $this->updateAddressDataStep($this->shippingAddressMock, $details['shippingAddress']);
+ $this->updateAddressDataStep($this->shippingAddress, $details['shippingAddress']);
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $addressMock
+ * Updates address details.
+ *
+ * @param MockObject $address
* @param array $addressData
*/
- private function updateAddressDataStep(\PHPUnit_Framework_MockObject_MockObject $addressMock, array $addressData)
+ private function updateAddressDataStep(MockObject $address, array $addressData): void
{
- $addressMock->expects(self::once())
- ->method('setStreet')
+ $address->method('setStreet')
->with([$addressData['streetAddress'], $addressData['extendedAddress']]);
- $addressMock->expects(self::once())
- ->method('setCity')
+ $address->method('setCity')
->with($addressData['locality']);
- $addressMock->expects(self::once())
- ->method('setRegionCode')
+ $address->method('setRegionCode')
->with($addressData['region']);
- $addressMock->expects(self::once())
- ->method('setCountryId')
+ $address->method('setCountryId')
->with($addressData['countryCodeAlpha2']);
- $addressMock->expects(self::once())
- ->method('setPostcode')
+ $address->method('setPostcode')
->with($addressData['postalCode']);
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Updates quote's address details.
+ *
+ * @param MockObject $quoteMock
* @param array $details
*/
- private function updateQuoteAddressStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock, array $details)
+ private function updateQuoteAddressStep(MockObject $quoteMock, array $details): void
{
$quoteMock->expects(self::exactly(2))
->method('getIsVirtual')
@@ -235,64 +234,61 @@ private function updateQuoteAddressStep(\PHPUnit_Framework_MockObject_MockObject
}
/**
+ * Updates billing address details.
+ *
* @param array $details
*/
- private function updateBillingAddressStep(array $details)
+ private function updateBillingAddressStep(array $details): void
{
- $this->configMock->expects(self::once())
- ->method('isRequiredBillingAddress')
+ $this->config->method('isRequiredBillingAddress')
->willReturn(true);
- $this->updateAddressDataStep($this->billingAddressMock, $details['billingAddress']);
+ $this->updateAddressDataStep($this->billingAddress, $details['billingAddress']);
- $this->billingAddressMock->expects(self::once())
- ->method('setLastname')
+ $this->billingAddress->method('setLastname')
->with($details['lastName']);
- $this->billingAddressMock->expects(self::once())
- ->method('setFirstname')
+ $this->billingAddress->method('setFirstname')
->with($details['firstName']);
- $this->billingAddressMock->expects(self::once())
- ->method('setEmail')
+ $this->billingAddress->method('setEmail')
->with($details['email']);
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Updates quote details.
+ *
+ * @param MockObject $quote
* @param array $details
*/
- private function updateQuoteStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock, array $details)
+ private function updateQuoteStep(MockObject $quote, array $details): void
{
- $quoteMock->expects(self::once())
- ->method('setMayEditShippingAddress')
+ $quote->method('setMayEditShippingAddress')
->with(false);
- $quoteMock->expects(self::once())
- ->method('setMayEditShippingMethod')
+ $quote->method('setMayEditShippingMethod')
->with(true);
- $quoteMock->expects(self::exactly(2))
- ->method('getShippingAddress')
- ->willReturn($this->shippingAddressMock);
- $quoteMock->expects(self::exactly(2))
+ $quote->method('getShippingAddress')
+ ->willReturn($this->shippingAddress);
+ $quote->expects(self::exactly(2))
->method('getBillingAddress')
- ->willReturn($this->billingAddressMock);
+ ->willReturn($this->billingAddress);
- $this->updateQuoteAddressStep($quoteMock, $details);
+ $this->updateQuoteAddressStep($quote, $details);
$this->disabledQuoteAddressValidationStep();
- $quoteMock->expects(self::once())
- ->method('collectTotals');
+ $quote->method('collectTotals');
- $this->quoteRepositoryMock->expects(self::once())
- ->method('save')
- ->with($quoteMock);
+ $this->quoteRepository->method('save')
+ ->with($quote);
}
/**
- * @return Quote|\PHPUnit_Framework_MockObject_MockObject
+ * Creates a mock for Quote object.
+ *
+ * @return Quote|MockObject
*/
- private function getQuoteMock()
+ private function getQuoteMock(): MockObject
{
- $quoteMock = $this->getMockBuilder(Quote::class)
+ $quote = $this->getMockBuilder(Quote::class)
->setMethods(
[
'getIsVirtual',
@@ -304,25 +300,27 @@ private function getQuoteMock()
'getBillingAddress',
'getExtensionAttributes'
]
- )->disableOriginalConstructor()
+ )
+ ->disableOriginalConstructor()
->getMock();
- $cartExtensionMock = $this->getMockBuilder(CartExtensionInterface::class)
+ $cartExtension = $this->getMockBuilder(CartExtensionInterface::class)
->setMethods(['setShippingAssignments'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $quoteMock->expects(self::any())
- ->method('getExtensionAttributes')
- ->willReturn($cartExtensionMock);
+ $quote->method('getExtensionAttributes')
+ ->willReturn($cartExtension);
- return $quoteMock;
+ return $quote;
}
/**
- * @return Payment|\PHPUnit_Framework_MockObject_MockObject
+ * Creates a mock for Payment object.
+ *
+ * @return Payment|MockObject
*/
- private function getPaymentMock()
+ private function getPaymentMock(): MockObject
{
return $this->getMockBuilder(Payment::class)
->disableOriginalConstructor()
diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv
index 6bf677151ed0d..7bd305f546dc6 100644
--- a/app/code/Magento/Braintree/i18n/en_US.csv
+++ b/app/code/Magento/Braintree/i18n/en_US.csv
@@ -192,3 +192,4 @@ Currency,Currency
"Too many concurrent attempts to refund this transaction. Try again later.","Too many concurrent attempts to refund this transaction. Try again later."
"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."
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 abf434bc6da26..eaebd8492b0a1 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/Catalog/Product/View/Type/Bundle.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php
index 62a2fa1c47e1e..fa488b073f515 100644
--- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php
+++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php
@@ -7,6 +7,7 @@
use Magento\Bundle\Model\Option;
use Magento\Catalog\Model\Product;
+use Magento\Framework\DataObject;
/**
* Catalog bundle product info block
@@ -170,7 +171,7 @@ public function getJsonConfig()
$defaultValues = [];
$preConfiguredFlag = $currentProduct->hasPreconfiguredValues();
- /** @var \Magento\Framework\DataObject|null $preConfiguredValues */
+ /** @var DataObject|null $preConfiguredValues */
$preConfiguredValues = $preConfiguredFlag ? $currentProduct->getPreconfiguredValues() : null;
$position = 0;
@@ -193,12 +194,13 @@ public function getJsonConfig()
$options[$optionId]['selections'][$configValue]['qty'] = $configQty;
}
}
+ $options = $this->processOptions($optionId, $options, $preConfiguredValues);
}
$position++;
}
$config = $this->getConfigData($currentProduct, $options);
- $configObj = new \Magento\Framework\DataObject(
+ $configObj = new DataObject(
[
'config' => $config,
]
@@ -403,4 +405,30 @@ private function getConfigData(Product $product, array $options)
];
return $config;
}
+
+ /**
+ * Set preconfigured quantities and selections to options.
+ *
+ * @param string $optionId
+ * @param array $options
+ * @param DataObject $preConfiguredValues
+ * @return array
+ */
+ private function processOptions(string $optionId, array $options, DataObject $preConfiguredValues)
+ {
+ $preConfiguredQtys = $preConfiguredValues->getData("bundle_option_qty/${optionId}") ?? [];
+ $selections = $options[$optionId]['selections'];
+ array_walk($selections, function (&$selection, $selectionId) use ($preConfiguredQtys) {
+ if (is_array($preConfiguredQtys) && isset($preConfiguredQtys[$selectionId])) {
+ $selection['qty'] = $preConfiguredQtys[$selectionId];
+ } else {
+ if ((int)$preConfiguredQtys > 0) {
+ $selection['qty'] = $preConfiguredQtys;
+ }
+ }
+ });
+ $options[$optionId]['selections'] = $selections;
+
+ return $options;
+ }
}
diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php
index 5d326e7c01d19..7c63af0bd0e2e 100644
--- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php
+++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php
@@ -167,7 +167,9 @@ protected function _getSelectedOptions()
*/
protected function assignSelection(\Magento\Bundle\Model\Option $option, $selectionId)
{
- if ($selectionId && $option->getSelectionById($selectionId)) {
+ if (is_array($selectionId)) {
+ $this->_selectedOptions = $selectionId;
+ } else if ($selectionId && $option->getSelectionById($selectionId)) {
$this->_selectedOptions = $selectionId;
} elseif (!$option->getRequired()) {
$this->_selectedOptions = 'None';
@@ -228,6 +230,8 @@ public function getProduct()
}
/**
+ * Get bundle option price title.
+ *
* @param \Magento\Catalog\Model\Product $selection
* @param bool $includeContainer
* @return string
diff --git a/app/code/Magento/Bundle/Model/OptionRepository.php b/app/code/Magento/Bundle/Model/OptionRepository.php
index 59e658b08df28..0b96ea8d5b789 100644
--- a/app/code/Magento/Bundle/Model/OptionRepository.php
+++ b/app/code/Magento/Bundle/Model/OptionRepository.php
@@ -90,7 +90,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($sku, $optionId)
{
@@ -106,23 +106,24 @@ public function get($sku, $optionId)
$productLinks = $this->linkManagement->getChildren($product->getSku(), $optionId);
- /** @var \Magento\Bundle\Api\Data\OptionInterface $option */
+ /** @var \Magento\Bundle\Api\Data\OptionInterface $optionDataObject */
$optionDataObject = $this->optionFactory->create();
$this->dataObjectHelper->populateWithArray(
$optionDataObject,
$option->getData(),
\Magento\Bundle\Api\Data\OptionInterface::class
);
- $optionDataObject->setOptionId($option->getId())
- ->setTitle($option->getTitle() === null ? $option->getDefaultTitle() : $option->getTitle())
- ->setSku($product->getSku())
- ->setProductLinks($productLinks);
+
+ $optionDataObject->setOptionId($option->getId());
+ $optionDataObject->setTitle($option->getTitle() === null ? $option->getDefaultTitle() : $option->getTitle());
+ $optionDataObject->setSku($product->getSku());
+ $optionDataObject->setProductLinks($productLinks);
return $optionDataObject;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList($sku)
{
@@ -131,6 +132,8 @@ public function getList($sku)
}
/**
+ * Return list of product options
+ *
* @param ProductInterface $product
* @return \Magento\Bundle\Api\Data\OptionInterface[]
*/
@@ -140,7 +143,7 @@ public function getListByProduct(ProductInterface $product)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(\Magento\Bundle\Api\Data\OptionInterface $option)
{
@@ -156,20 +159,19 @@ public function delete(\Magento\Bundle\Api\Data\OptionInterface $option)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteById($sku, $optionId)
{
- $product = $this->getProduct($sku);
- $optionCollection = $this->type->getOptionsCollection($product);
- $optionCollection->setIdFilter($optionId);
- $hasBeenDeleted = $this->delete($optionCollection->getFirstItem());
+ /** @var \Magento\Bundle\Api\Data\OptionInterface $option */
+ $option = $this->get($sku, $optionId);
+ $hasBeenDeleted = $this->delete($option);
return $hasBeenDeleted;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(
\Magento\Catalog\Api\Data\ProductInterface $product,
@@ -189,6 +191,9 @@ public function save(
* @param \Magento\Catalog\Api\Data\ProductInterface $product
* @param \Magento\Bundle\Api\Data\OptionInterface $option
* @return $this
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @throws \Magento\Framework\Exception\CouldNotSaveException
*/
protected function updateOptionSelection(
\Magento\Catalog\Api\Data\ProductInterface $product,
@@ -228,9 +233,12 @@ protected function updateOptionSelection(
}
/**
+ * Retrieve product by SKU
+ *
* @param string $sku
* @return \Magento\Catalog\Api\Data\ProductInterface
- * @throws \Magento\Framework\Exception\InputException
+ * @throws InputException
+ * @throws NoSuchEntityException
*/
private function getProduct($sku)
{
diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php
index 92bada8094c7e..c8cd8c15fdb8e 100644
--- a/app/code/Magento/Bundle/Model/Product/Type.php
+++ b/app/code/Magento/Bundle/Model/Product/Type.php
@@ -308,8 +308,11 @@ public function getSku($product)
$selectionIds = $this->serializer->unserialize($customOption->getValue());
if (!empty($selectionIds)) {
$selections = $this->getSelectionsByIds($selectionIds, $product);
- foreach ($selections->getItems() as $selection) {
- $skuParts[] = $selection->getSku();
+ foreach ($selectionIds as $selectionId) {
+ $entity = $selections->getItemByColumnValue('selection_id', $selectionId);
+ if (isset($entity) && $entity->getEntityId()) {
+ $skuParts[] = $entity->getSku();
+ }
}
}
}
@@ -736,7 +739,7 @@ protected function _prepareProduct(\Magento\Framework\DataObject $buyRequest, $p
$price = $product->getPriceModel()
->getSelectionFinalTotalPrice($product, $selection, 0, $qty);
$attributes = [
- 'price' => $this->priceCurrency->convert($price),
+ 'price' => $price,
'qty' => $qty,
'option_label' => $selection->getOption()
->getTitle(),
@@ -820,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/Plugin/UpdatePriceInQuoteItemOptions.php b/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php
new file mode 100644
index 0000000000000..d5aafb8ad2b61
--- /dev/null
+++ b/app/code/Magento/Bundle/Plugin/UpdatePriceInQuoteItemOptions.php
@@ -0,0 +1,55 @@
+serializer = $serializer;
+ }
+
+ /**
+ * Update price on quote item options level
+ *
+ * @param OrigQuoteItem $subject
+ * @param AbstractItem $result
+ * @return AbstractItem
+ *
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterCalcRowTotal(OrigQuoteItem $subject, AbstractItem $result)
+ {
+ $bundleAttributes = $result->getProduct()->getCustomOption('bundle_selection_attributes');
+ if ($bundleAttributes !== null) {
+ $actualPrice = $result->getPrice();
+ $parsedValue = $this->serializer->unserialize($bundleAttributes->getValue());
+ if (is_array($parsedValue) && array_key_exists('price', $parsedValue)) {
+ $parsedValue['price'] = $actualPrice;
+ }
+ $bundleAttributes->setValue($this->serializer->serialize($parsedValue));
+ }
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml
new file mode 100644
index 0000000000000..441303e8f1b84
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StoreFrontAddProductToCartFromBundleActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
index 8d9f29814f762..946992f1efe04 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
@@ -24,10 +24,13 @@
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index 735571375866e..fae1ec331b667 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -13,5 +13,6 @@
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml
index 3a70b189b4dce..c6a07f7ed95c3 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/CurrencyChangingBundleProductInCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml
new file mode 100644
index 0000000000000..ded8bb3c83337
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/CurrencyChangingBundleProductInCartTest.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml
index 0fb8a7b88250c..5b2b771434b73 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/MassEnableDisableBundleProductsTest.xml
@@ -17,9 +17,6 @@
-
-
-
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml
index f94cd83f4e7d7..58806126aee30 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml
@@ -94,9 +94,8 @@
-
-
-
+
+
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php
index b4a466b413af0..2450f63c38933 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php
@@ -8,6 +8,7 @@
namespace Magento\Bundle\Test\Unit\Model;
use Magento\Bundle\Model\OptionRepository;
+use Magento\Framework\Exception\NoSuchEntityException;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -84,7 +85,7 @@ protected function setUp()
->getMock();
$this->optionResourceMock = $this->createPartialMock(
\Magento\Bundle\Model\ResourceModel\Option::class,
- ['delete', '__wakeup', 'save', 'removeOptionSelections']
+ ['get', 'delete', '__wakeup', 'save', 'removeOptionSelections']
);
$this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
$this->linkManagementMock = $this->createMock(\Magento\Bundle\Api\ProductLinkManagementInterface::class);
@@ -227,32 +228,92 @@ public function testDeleteThrowsExceptionIfCannotDelete()
$this->model->delete($optionMock);
}
+ /**
+ * Test successful delete action for given $optionId
+ */
public function testDeleteById()
{
$productSku = 'sku';
$optionId = 100;
- $productMock = $this->createMock(\Magento\Catalog\Api\Data\ProductInterface::class);
+
+ $optionMock = $this->createMock(\Magento\Bundle\Model\Option::class);
+ $optionMock->expects($this->exactly(2))
+ ->method('getId')
+ ->willReturn($optionId);
+
+ $optionMock->expects($this->once())
+ ->method('getData')
+ ->willReturn([
+ 'title' => 'Option title',
+ 'option_id' => $optionId
+ ]);
+
+ $this->optionFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($optionMock);
+
+ $productMock = $this->createPartialMock(
+ \Magento\Catalog\Model\Product::class,
+ ['getTypeId', 'getTypeInstance', 'getStoreId', 'getPriceType', '__wakeup', 'getSku']
+ );
$productMock->expects($this->once())
->method('getTypeId')
->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE);
- $this->productRepositoryMock->expects($this->once())
+ $productMock->expects($this->exactly(2))->method('getSku')->willReturn($productSku);
+
+ $this->productRepositoryMock
+ ->expects($this->once())
->method('get')
->with($productSku)
->willReturn($productMock);
+ $optCollectionMock = $this->createMock(\Magento\Bundle\Model\ResourceModel\Option\Collection::class);
+ $optCollectionMock->expects($this->once())->method('getItemById')->with($optionId)->willReturn($optionMock);
+ $this->typeMock->expects($this->once())
+ ->method('getOptionsCollection')
+ ->with($productMock)
+ ->willReturn($optCollectionMock);
+
+ $this->assertTrue($this->model->deleteById($productSku, $optionId));
+ }
+
+ /**
+ * Tests if NoSuchEntityException thrown when provided $optionId not found
+ */
+ public function testDeleteByIdException()
+ {
+ $productSku = 'sku';
+ $optionId = null;
+
$optionMock = $this->createMock(\Magento\Bundle\Model\Option::class);
+ $optionMock->expects($this->exactly(1))
+ ->method('getId')
+ ->willReturn($optionId);
+
+ $productMock = $this->createPartialMock(
+ \Magento\Catalog\Model\Product::class,
+ ['getTypeId', 'getTypeInstance', 'getStoreId', 'getPriceType', '__wakeup', 'getSku']
+ );
+ $productMock->expects($this->once())
+ ->method('getTypeId')
+ ->willReturn(\Magento\Catalog\Model\Product\Type::TYPE_BUNDLE);
+
+ $this->productRepositoryMock
+ ->expects($this->once())
+ ->method('get')
+ ->with($productSku)
+ ->willReturn($productMock);
$optCollectionMock = $this->createMock(\Magento\Bundle\Model\ResourceModel\Option\Collection::class);
+ $optCollectionMock->expects($this->once())->method('getItemById')->with($optionId)->willReturn($optionMock);
$this->typeMock->expects($this->once())
->method('getOptionsCollection')
->with($productMock)
->willReturn($optCollectionMock);
- $optCollectionMock->expects($this->once())->method('setIdFilter')->with($optionId)->willReturnSelf();
- $optCollectionMock->expects($this->once())->method('getFirstItem')->willReturn($optionMock);
+ $this->expectException(NoSuchEntityException::class);
- $this->optionResourceMock->expects($this->once())->method('delete')->with($optionMock)->willReturnSelf();
- $this->assertTrue($this->model->deleteById($productSku, $optionId));
+ $this->model->deleteById($productSku, $optionId);
}
/**
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php
index 4bbf5641c55d3..59f7f008ed3ee 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php
@@ -513,10 +513,6 @@ function ($key) use ($optionCollection, $selectionCollection) {
->method('getSelectionId')
->willReturn(314);
- $this->priceCurrency->expects($this->once())
- ->method('convert')
- ->willReturn(3.14);
-
$result = $this->model->prepareForCartAdvanced($buyRequest, $product);
$this->assertEquals([$product, $productType], $result);
}
@@ -737,10 +733,6 @@ function ($key) use ($optionCollection, $selectionCollection) {
->method('prepareForCart')
->willReturn([]);
- $this->priceCurrency->expects($this->once())
- ->method('convert')
- ->willReturn(3.14);
-
$result = $this->model->prepareForCartAdvanced($buyRequest, $product);
$this->assertEquals('We can\'t add this item to your shopping cart right now.', $result);
}
@@ -961,10 +953,6 @@ function ($key) use ($optionCollection, $selectionCollection) {
->method('prepareForCart')
->willReturn('string');
- $this->priceCurrency->expects($this->once())
- ->method('convert')
- ->willReturn(3.14);
-
$result = $this->model->prepareForCartAdvanced($buyRequest, $product);
$this->assertEquals('string', $result);
}
@@ -1595,7 +1583,7 @@ public function testGetSkuWithoutType()
->disableOriginalConstructor()
->getMock();
$selectionItemMock = $this->getMockBuilder(\Magento\Framework\DataObject::class)
- ->setMethods(['getSku', '__wakeup'])
+ ->setMethods(['getSku', 'getEntityId', '__wakeup'])
->disableOriginalConstructor()
->getMock();
@@ -1623,9 +1611,12 @@ public function testGetSkuWithoutType()
->will($this->returnValue($serializeIds));
$selectionMock = $this->getSelectionsByIdsMock($selectionIds, $productMock, 5, 6);
$selectionMock->expects(($this->any()))
- ->method('getItems')
- ->will($this->returnValue([$selectionItemMock]));
- $selectionItemMock->expects($this->any())
+ ->method('getItemByColumnValue')
+ ->will($this->returnValue($selectionItemMock));
+ $selectionItemMock->expects($this->at(0))
+ ->method('getEntityId')
+ ->will($this->returnValue(1));
+ $selectionItemMock->expects($this->once())
->method('getSku')
->will($this->returnValue($itemSku));
diff --git a/app/code/Magento/Bundle/etc/db_schema.xml b/app/code/Magento/Bundle/etc/db_schema.xml
index f1c1e7ae782dc..33738cd252d61 100644
--- a/app/code/Magento/Bundle/etc/db_schema.xml
+++ b/app/code/Magento/Bundle/etc/db_schema.xml
@@ -18,13 +18,13 @@
-
+
-
-
+
@@ -39,13 +39,13 @@
-
+
-
-
+
@@ -73,19 +73,19 @@
comment="Selection Qty"/>
-
+
-
-
-
+
-
+
@@ -101,19 +101,19 @@
nullable="false" default="0" comment="Selection Price Value"/>
-
+
-
-
-
+
@@ -129,24 +129,24 @@
comment="Min Price"/>
-
+
-
-
-
-
+
-
+
@@ -162,7 +162,7 @@
default="0" comment="Option Id"/>
-
+
@@ -197,7 +197,7 @@
comment="Tier Price"/>
-
+
@@ -231,7 +231,7 @@
comment="Tier Price"/>
-
+
@@ -257,7 +257,7 @@
comment="Price"/>
-
+
@@ -285,7 +285,7 @@
comment="Price"/>
-
+
@@ -313,7 +313,7 @@
comment="Tier Price"/>
-
+
@@ -340,7 +340,7 @@
comment="Tier Price"/>
-
+
diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml
index 733b089dccd4b..72155d922a25f 100644
--- a/app/code/Magento/Bundle/etc/di.xml
+++ b/app/code/Magento/Bundle/etc/di.xml
@@ -123,6 +123,9 @@
+
+
+
@@ -140,6 +143,13 @@
+
+
+
+ - Magento\Bundle\Model\ProductOptionProcessor
+
+
+
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItemLinks.php b/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItemLinks.php
index f55028a7d1a5b..184f7177a995c 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItemLinks.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItemLinks.php
@@ -7,7 +7,7 @@
namespace Magento\BundleGraphQl\Model\Resolver;
-use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\BundleGraphQl\Model\Resolver\Links\Collection;
use Magento\Framework\GraphQl\Config\Element\Field;
@@ -47,7 +47,7 @@ public function __construct(
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
if (!isset($value['option_id']) || !isset($value['parent_id'])) {
- throw new GraphQlInputException(__('"option_id" and "parent_id" values should be specified'));
+ throw new LocalizedException(__('"option_id" and "parent_id" values should be specified'));
}
$this->linkCollection->addIdFilters((int)$value['option_id'], (int)$value['parent_id']);
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
index bcddd5d084629..de72b18982c12 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
@@ -7,7 +7,7 @@
namespace Magento\BundleGraphQl\Model\Resolver\Options;
-use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\Product as ProductDataProvider;
use Magento\Framework\GraphQl\Config\Element\Field;
@@ -50,7 +50,7 @@ public function resolve(
array $args = null
) {
if (!isset($value['sku'])) {
- throw new GraphQlInputException(__('"sku" value should be specified'));
+ throw new LocalizedException(__('"sku" value should be specified'));
}
$this->product->addProductSku($value['sku']);
diff --git a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php
index 3ed7e144ddd5a..81a47d72602b7 100644
--- a/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php
+++ b/app/code/Magento/BundleImportExport/Model/Import/Product/Type/Bundle.php
@@ -20,6 +20,7 @@
/**
* Class Bundle
+ *
* @package Magento\BundleImportExport\Model\Import\Product\Type
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/
@@ -349,6 +350,8 @@ protected function populateSelectionTemplate($selection, $optionId, $parentId, $
}
/**
+ * Deprecated method for retrieving mapping between skus and products.
+ *
* @deprecated Misspelled method
* @see retrieveProductsByCachedSkus
*/
@@ -600,6 +603,7 @@ protected function insertOptions()
/**
* Populate array for insert option values
+ *
* @param array $optionIds
* @return array
*/
@@ -779,7 +783,7 @@ protected function clear()
*/
private function getStoreIdByCode(string $storeCode): int
{
- if (!isset($this->storeIdToCode[$storeCode])) {
+ if (!isset($this->storeCodeToId[$storeCode])) {
/** @var $store \Magento\Store\Model\Store */
foreach ($this->storeManager->getStores() as $store) {
$this->storeCodeToId[$store->getCode()] = $store->getId();
diff --git a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
index 8acf170d43cfb..727e18280d76f 100644
--- a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
+++ b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
@@ -7,6 +7,9 @@
use Magento\Framework\Cache\InvalidateLogger;
+/**
+ * Class PurgeCache
+ */
class PurgeCache
{
const HEADER_X_MAGENTO_TAGS_PATTERN = 'X-Magento-Tags-Pattern';
@@ -26,6 +29,18 @@ class PurgeCache
*/
private $logger;
+ /**
+ * Batch size of the purge request.
+ *
+ * Based on default Varnish 4 http_req_hdr_len size minus a 512 bytes margin for method,
+ * header name, line feeds etc.
+ *
+ * @see https://varnish-cache.org/docs/4.1/reference/varnishd.html
+ *
+ * @var int
+ */
+ private $requestSize = 7680;
+
/**
* Constructor
*
@@ -44,18 +59,65 @@ public function __construct(
}
/**
- * Send curl purge request
- * to invalidate cache by tags pattern
+ * Send curl purge request to invalidate cache by tags pattern
*
* @param string $tagsPattern
* @return bool Return true if successful; otherwise return false
*/
public function sendPurgeRequest($tagsPattern)
{
+ $successful = true;
$socketAdapter = $this->socketAdapterFactory->create();
$servers = $this->cacheServer->getUris();
- $headers = [self::HEADER_X_MAGENTO_TAGS_PATTERN => $tagsPattern];
$socketAdapter->setOptions(['timeout' => 10]);
+
+ $formattedTagsChunks = $this->splitTags($tagsPattern);
+ foreach ($formattedTagsChunks as $formattedTagsChunk) {
+ if (!$this->sendPurgeRequestToServers($socketAdapter, $servers, $formattedTagsChunk)) {
+ $successful = false;
+ }
+ }
+
+ return $successful;
+ }
+
+ /**
+ * Split tags by batches
+ *
+ * @param string $tagsPattern
+ * @return \Generator
+ */
+ private function splitTags($tagsPattern)
+ {
+ $tagsBatchSize = 0;
+ $formattedTagsChunk = [];
+ $formattedTags = explode('|', $tagsPattern);
+ foreach ($formattedTags as $formattedTag) {
+ if ($tagsBatchSize + strlen($formattedTag) > $this->requestSize - count($formattedTagsChunk) - 1) {
+ yield implode('|', array_unique($formattedTagsChunk));
+ $formattedTagsChunk = [];
+ $tagsBatchSize = 0;
+ }
+
+ $tagsBatchSize += strlen($formattedTag);
+ $formattedTagsChunk[] = $formattedTag;
+ }
+ if (!empty($formattedTagsChunk)) {
+ yield implode('|', array_unique($formattedTagsChunk));
+ }
+ }
+
+ /**
+ * Send curl purge request to servers to invalidate cache by tags pattern
+ *
+ * @param \Zend\Http\Client\Adapter\Socket $socketAdapter
+ * @param \Zend\Uri\Uri[] $servers
+ * @param string $formattedTagsChunk
+ * @return bool Return true if successful; otherwise return false
+ */
+ private function sendPurgeRequestToServers($socketAdapter, $servers, $formattedTagsChunk)
+ {
+ $headers = [self::HEADER_X_MAGENTO_TAGS_PATTERN => $formattedTagsChunk];
foreach ($servers as $server) {
$headers['Host'] = $server->getHost();
try {
@@ -69,12 +131,11 @@ public function sendPurgeRequest($tagsPattern)
$socketAdapter->read();
$socketAdapter->close();
} catch (\Exception $e) {
- $this->logger->critical($e->getMessage(), compact('server', 'tagsPattern'));
+ $this->logger->critical($e->getMessage(), compact('server', 'formattedTagsChunk'));
return false;
}
}
-
- $this->logger->execute(compact('servers', 'tagsPattern'));
+ $this->logger->execute(compact('servers', 'formattedTagsChunk'));
return true;
}
}
diff --git a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
index 91f3a785df36b..f04e9ac8611bc 100644
--- a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
+++ b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
@@ -3,13 +3,19 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Captcha\Model\Customer\Plugin;
use Magento\Captcha\Helper\Data as CaptchaHelper;
-use Magento\Framework\Session\SessionManagerInterface;
+use Magento\Customer\Controller\Ajax\Login;
+use Magento\Framework\Controller\Result\Json;
use Magento\Framework\Controller\Result\JsonFactory;
+use Magento\Framework\Session\SessionManagerInterface;
+/**
+ * The plugin for ajax login controller.
+ */
class AjaxLogin
{
/**
@@ -61,14 +67,14 @@ public function __construct(
}
/**
- * @param \Magento\Customer\Controller\Ajax\Login $subject
+ * Validates captcha during request execution.
+ *
+ * @param Login $subject
* @param \Closure $proceed
* @return $this
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function aroundExecute(
- \Magento\Customer\Controller\Ajax\Login $subject,
+ Login $subject,
\Closure $proceed
) {
$captchaFormIdField = 'captcha_form_id';
@@ -93,26 +99,31 @@ public function aroundExecute(
foreach ($this->formIds as $formId) {
if ($formId === $loginFormId) {
$captchaModel = $this->helper->getCaptcha($formId);
+
if ($captchaModel->isRequired($username)) {
- $captchaModel->logAttempt($username);
if (!$captchaModel->isCorrect($captchaString)) {
$this->sessionManager->setUsername($username);
- return $this->returnJsonError(__('Incorrect CAPTCHA'));
+ $captchaModel->logAttempt($username);
+ return $this->returnJsonError(__('Incorrect CAPTCHA'), true);
}
}
+
+ $captchaModel->logAttempt($username);
}
}
return $proceed();
}
/**
+ * Gets Json response.
*
* @param \Magento\Framework\Phrase $phrase
- * @return \Magento\Framework\Controller\Result\Json
+ * @param bool $isCaptchaRequired
+ * @return Json
*/
- private function returnJsonError(\Magento\Framework\Phrase $phrase): \Magento\Framework\Controller\Result\Json
+ private function returnJsonError(\Magento\Framework\Phrase $phrase, bool $isCaptchaRequired = false): Json
{
$resultJson = $this->resultJsonFactory->create();
- return $resultJson->setData(['errors' => true, 'message' => $phrase]);
+ return $resultJson->setData(['errors' => true, 'message' => $phrase, 'captcha' => $isCaptchaRequired]);
}
}
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
index 71a876bbbcdbf..beb2c2bffa135 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
@@ -13,7 +13,7 @@
-
+
diff --git a/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php b/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php
index ec2a49f3fc566..0764ea36897d1 100644
--- a/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Model/Customer/Plugin/AjaxLoginTest.php
@@ -158,7 +158,7 @@ public function testAroundExecuteIncorrectCaptcha()
$this->resultJsonMock
->expects($this->once())
->method('setData')
- ->with(['errors' => true, 'message' => __('Incorrect CAPTCHA')])
+ ->with(['errors' => true, 'message' => __('Incorrect CAPTCHA'), 'captcha' => true])
->will($this->returnSelf());
$closure = function () {
diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginBackendObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginBackendObserverTest.php
new file mode 100644
index 0000000000000..415f022a7364d
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginBackendObserverTest.php
@@ -0,0 +1,137 @@
+helperMock = $this->createMock(Data::class);
+ $this->messageManagerMock = $this->createMock(ManagerInterface::class);
+ $this->captchaStringResolverMock = $this->createMock(CaptchaStringResolver::class);
+ $this->requestMock = $this->createMock(RequestInterface::class);
+
+ $this->observer = new CheckUserLoginBackendObserver(
+ $this->helperMock,
+ $this->captchaStringResolverMock,
+ $this->requestMock
+ );
+ }
+
+ /**
+ * Test check user login in backend with correct captcha
+ *
+ * @dataProvider requiredCaptchaDataProvider
+ * @param bool $isRequired
+ * @return void
+ */
+ public function testCheckOnBackendLoginWithCorrectCaptcha(bool $isRequired): void
+ {
+ $formId = 'backend_login';
+ $login = 'admin';
+ $captchaValue = 'captcha-value';
+
+ /** @var Observer|MockObject $observerMock */
+ $observerMock = $this->createPartialMock(Observer::class, ['getEvent']);
+ $eventMock = $this->createPartialMock(Event::class, ['getUsername']);
+ $captcha = $this->createMock(DefaultModel::class);
+
+ $eventMock->method('getUsername')->willReturn('admin');
+ $observerMock->method('getEvent')->willReturn($eventMock);
+ $captcha->method('isRequired')->with($login)->willReturn($isRequired);
+ $captcha->method('isCorrect')->with($captchaValue)->willReturn(true);
+ $this->helperMock->method('getCaptcha')->with($formId)->willReturn($captcha);
+ $this->captchaStringResolverMock->method('resolve')->with($this->requestMock, $formId)
+ ->willReturn($captchaValue);
+
+ $this->observer->execute($observerMock);
+ }
+
+ /**
+ * @return array
+ */
+ public function requiredCaptchaDataProvider(): array
+ {
+ return [
+ [true],
+ [false]
+ ];
+ }
+
+ /**
+ * Test check user login in backend with wrong captcha
+ *
+ * @return void
+ * @expectedException \Magento\Framework\Exception\Plugin\AuthenticationException
+ */
+ public function testCheckOnBackendLoginWithWrongCaptcha(): void
+ {
+ $formId = 'backend_login';
+ $login = 'admin';
+ $captchaValue = 'captcha-value';
+
+ /** @var Observer|MockObject $observerMock */
+ $observerMock = $this->createPartialMock(Observer::class, ['getEvent']);
+ $eventMock = $this->createPartialMock(Event::class, ['getUsername']);
+ $captcha = $this->createMock(DefaultModel::class);
+
+ $eventMock->method('getUsername')->willReturn($login);
+ $observerMock->method('getEvent')->willReturn($eventMock);
+ $captcha->method('isRequired')->with($login)->willReturn(true);
+ $captcha->method('isCorrect')->with($captchaValue)->willReturn(false);
+ $this->helperMock->method('getCaptcha')->with($formId)->willReturn($captcha);
+ $this->captchaStringResolverMock->method('resolve')->with($this->requestMock, $formId)
+ ->willReturn($captchaValue);
+
+ $this->observer->execute($observerMock);
+ }
+}
diff --git a/app/code/Magento/Captcha/etc/db_schema.xml b/app/code/Magento/Captcha/etc/db_schema.xml
index fa9a14abb8963..b8987363569ca 100644
--- a/app/code/Magento/Captcha/etc/db_schema.xml
+++ b/app/code/Magento/Captcha/etc/db_schema.xml
@@ -13,7 +13,7 @@
-
+
diff --git a/app/code/Magento/Captcha/etc/di.xml b/app/code/Magento/Captcha/etc/di.xml
index 3a929f5e6cc00..83c4e8aa1e2c1 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/view/frontend/web/js/model/captcha.js b/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js
index 3a235df73a916..52968e507e6bf 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,7 +17,7 @@ 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,
@@ -41,7 +41,7 @@ define([
* @return {Boolean}
*/
getIsVisible: function () {
- return this.visibility;
+ return this.visibility();
},
/**
@@ -55,14 +55,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 f80b2ab163ffd..f78b848312702 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
@@ -89,6 +89,13 @@ define([
return this.currentCaptcha !== null ? this.currentCaptcha.getIsRequired() : false;
},
+ /**
+ * @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 7709febea60a3..a8efd0865bbb8 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
@@ -4,34 +4,44 @@
*/
define([
- 'Magento_Captcha/js/view/checkout/defaultCaptcha',
- 'Magento_Captcha/js/model/captchaList',
- 'Magento_Customer/js/action/login'
-],
-function (defaultCaptcha, captchaList, loginAction) {
- 'use strict';
-
- return defaultCaptcha.extend({
- /** @inheritdoc */
- initialize: function () {
- var self = this,
- currentCaptcha;
-
- this._super();
- currentCaptcha = captchaList.getCaptchaByFormId(this.formId);
-
- if (currentCaptcha != null) {
- currentCaptcha.setIsVisible(true);
- this.setCurrentCaptcha(currentCaptcha);
-
- loginAction.registerLoginCallback(function (loginData) {
- if (loginData['captcha_form_id'] &&
- loginData['captcha_form_id'] == self.formId //eslint-disable-line eqeqeq
- ) {
+ 'underscore',
+ 'Magento_Captcha/js/view/checkout/defaultCaptcha',
+ 'Magento_Captcha/js/model/captchaList',
+ 'Magento_Customer/js/action/login'
+ ],
+ function (_, defaultCaptcha, captchaList, loginAction) {
+ 'use strict';
+
+ return defaultCaptcha.extend({
+ /** @inheritdoc */
+ initialize: function () {
+ var self = this,
+ currentCaptcha;
+
+ this._super();
+ currentCaptcha = captchaList.getCaptchaByFormId(this.formId);
+
+ if (currentCaptcha != null) {
+ currentCaptcha.setIsVisible(true);
+ this.setCurrentCaptcha(currentCaptcha);
+
+ loginAction.registerLoginCallback(function (loginData, response) {
+ if (!loginData['captcha_form_id'] || loginData['captcha_form_id'] !== self.formId) {
+ return;
+ }
+
+ if (_.isUndefined(response) || !response.errors) {
+ return;
+ }
+
+ // check if captcha should be required after login attempt
+ if (!self.isRequired() && response.captcha && self.isRequired() !== response.captcha) {
+ self.setIsRequired(response.captcha);
+ }
+
self.refresh();
- }
- });
+ });
+ }
}
- }
+ });
});
-});
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 575b3ca6f732e..8923c81bf4bb3 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,14 @@
* See COPYING.txt for license details.
*/
-->
+
+
+
-
-
+
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php
index ee92fd7c19b80..26ffc6e0df3d9 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php
@@ -11,6 +11,9 @@
use Magento\Backend\Block\Widget\Form;
+/**
+ * Form group for attribute set
+ */
class Formgroup extends \Magento\Backend\Block\Widget\Form\Generic
{
/**
@@ -37,6 +40,8 @@ public function __construct(
}
/**
+ * Prepare form elements
+ *
* @return void
*/
protected function _prepareForm()
@@ -77,13 +82,15 @@ protected function _prepareForm()
}
/**
+ * Returns set id
+ *
* @return int
*/
protected function _getSetId()
{
- return intval(
+ return (int)(
$this->getRequest()->getParam('id')
- ) > 0 ? intval(
+ ) > 0 ? (int)(
$this->getRequest()->getParam('id')
) : $this->_typeFactory->create()->load(
$this->_coreRegistry->registry('entityType')
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 a08142c10be5e..2df0ff0b6cd7c 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/Adminhtml/Product/Helper/Form/Gallery/Content.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php
index e1208e25cc7c8..063503682f4db 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php
@@ -13,10 +13,12 @@
*/
namespace Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery;
+use Magento\Framework\App\ObjectManager;
use Magento\Backend\Block\Media\Uploader;
use Magento\Framework\View\Element\AbstractBlock;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\FileSystemException;
+use Magento\Backend\Block\DataProviders\ImageUploadConfig as ImageUploadConfigDataProvider;
/**
* Block for gallery content.
@@ -43,21 +45,30 @@ class Content extends \Magento\Backend\Block\Widget
*/
private $imageHelper;
+ /**
+ * @var ImageUploadConfigDataProvider
+ */
+ private $imageUploadConfigDataProvider;
+
/**
* @param \Magento\Backend\Block\Template\Context $context
* @param \Magento\Framework\Json\EncoderInterface $jsonEncoder
* @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig
* @param array $data
+ * @param ImageUploadConfigDataProvider $imageUploadConfigDataProvider
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Framework\Json\EncoderInterface $jsonEncoder,
\Magento\Catalog\Model\Product\Media\Config $mediaConfig,
- array $data = []
+ array $data = [],
+ ImageUploadConfigDataProvider $imageUploadConfigDataProvider = null
) {
$this->_jsonEncoder = $jsonEncoder;
$this->_mediaConfig = $mediaConfig;
parent::__construct($context, $data);
+ $this->imageUploadConfigDataProvider = $imageUploadConfigDataProvider
+ ?: ObjectManager::getInstance()->get(ImageUploadConfigDataProvider::class);
}
/**
@@ -67,7 +78,11 @@ public function __construct(
*/
protected function _prepareLayout()
{
- $this->addChild('uploader', \Magento\Backend\Block\Media\Uploader::class);
+ $this->addChild(
+ 'uploader',
+ \Magento\Backend\Block\Media\Uploader::class,
+ ['image_upload_config_data' => $this->imageUploadConfigDataProvider]
+ );
$this->getUploader()->getConfig()->setUrl(
$this->_urlBuilder->addSessionParam()->getUrl('catalog/product_gallery/upload')
diff --git a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php
index 4102c82a0a316..c8da0f70f73b6 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/Compare/ListCompare.php b/app/code/Magento/Catalog/Block/Product/Compare/ListCompare.php
index 6c54aa4e171ef..76f5dbd1bea88 100644
--- a/app/code/Magento/Catalog/Block/Product/Compare/ListCompare.php
+++ b/app/code/Magento/Catalog/Block/Product/Compare/ListCompare.php
@@ -122,12 +122,7 @@ public function __construct(
*/
public function getAddToWishlistParams($product)
{
- $continueUrl = $this->urlEncoder->encode($this->getUrl('customer/account'));
- $urlParamName = Action::PARAM_NAME_URL_ENCODED;
-
- $continueUrlParams = [$urlParamName => $continueUrl];
-
- return $this->_wishlistHelper->getAddParams($product, $continueUrlParams);
+ return $this->_wishlistHelper->getAddParams($product);
}
/**
diff --git a/app/code/Magento/Catalog/Block/Product/Image.php b/app/code/Magento/Catalog/Block/Product/Image.php
index 20a556ab41451..7a7f9c0affc7d 100644
--- a/app/code/Magento/Catalog/Block/Product/Image.php
+++ b/app/code/Magento/Catalog/Block/Product/Image.php
@@ -6,6 +6,8 @@
namespace Magento\Catalog\Block\Product;
/**
+ * Product image block
+ *
* @api
* @method string getImageUrl()
* @method string getWidth()
@@ -13,6 +15,7 @@
* @method string getLabel()
* @method float getRatio()
* @method string getCustomAttributes()
+ * @method string getClass()
* @since 100.0.2
*/
class Image extends \Magento\Framework\View\Element\Template
diff --git a/app/code/Magento/Catalog/Block/Product/ImageFactory.php b/app/code/Magento/Catalog/Block/Product/ImageFactory.php
index f9a576367ddeb..aa303af656a5b 100644
--- a/app/code/Magento/Catalog/Block/Product/ImageFactory.php
+++ b/app/code/Magento/Catalog/Block/Product/ImageFactory.php
@@ -77,16 +77,29 @@ private function getStringCustomAttributes(array $attributes): string
{
$result = [];
foreach ($attributes as $name => $value) {
- $result[] = $name . '="' . $value . '"';
+ if ($name != 'class') {
+ $result[] = $name . '="' . $value . '"';
+ }
}
return !empty($result) ? implode(' ', $result) : '';
}
+ /**
+ * Retrieve image class for HTML element
+ *
+ * @param array $attributes
+ * @return string
+ */
+ private function getClass(array $attributes): string
+ {
+ return $attributes['class'] ?? 'product-image-photo';
+ }
+
/**
* Calculate image ratio
*
- * @param $width
- * @param $height
+ * @param int $width
+ * @param int $height
* @return float
*/
private function getRatio(int $width, int $height): float
@@ -98,8 +111,9 @@ private function getRatio(int $width, int $height): float
}
/**
- * @param Product $product
+ * Get image label
*
+ * @param Product $product
* @param string $imageType
* @return string
*/
@@ -114,6 +128,7 @@ private function getLabel(Product $product, string $imageType): string
/**
* Create image block from product
+ *
* @param Product $product
* @param string $imageId
* @param array|null $attributes
@@ -154,6 +169,7 @@ public function create(Product $product, string $imageId, array $attributes = nu
'label' => $this->getLabel($product, $imageMiscParams['image_type']),
'ratio' => $this->getRatio($imageMiscParams['image_width'], $imageMiscParams['image_height']),
'custom_attributes' => $this->getStringCustomAttributes($attributes),
+ 'class' => $this->getClass($attributes),
'product_id' => $product->getId()
],
];
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
index 0c547f81c85d6..596cd7cc5bdce 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 219922f9e46d5..6de70bb971367 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/Toolbar.php b/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php
index 0b8d2d6c89e72..c530ba4785ad9 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php
@@ -7,6 +7,8 @@
use Magento\Catalog\Helper\Product\ProductList;
use Magento\Catalog\Model\Product\ProductList\Toolbar as ToolbarModel;
+use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer;
+use Magento\Framework\App\ObjectManager;
/**
* Product list toolbar
@@ -77,6 +79,7 @@ class Toolbar extends \Magento\Framework\View\Element\Template
/**
* @var bool $_paramsMemorizeAllowed
+ * @deprecated
*/
protected $_paramsMemorizeAllowed = true;
@@ -96,6 +99,7 @@ class Toolbar extends \Magento\Framework\View\Element\Template
* Catalog session
*
* @var \Magento\Catalog\Model\Session
+ * @deprecated
*/
protected $_catalogSession;
@@ -104,6 +108,11 @@ class Toolbar extends \Magento\Framework\View\Element\Template
*/
protected $_toolbarModel;
+ /**
+ * @var ToolbarMemorizer
+ */
+ private $toolbarMemorizer;
+
/**
* @var ProductList
*/
@@ -119,6 +128,16 @@ class Toolbar extends \Magento\Framework\View\Element\Template
*/
protected $_postDataHelper;
+ /**
+ * @var \Magento\Framework\App\Http\Context
+ */
+ private $httpContext;
+
+ /**
+ * @var \Magento\Framework\Data\Form\FormKey
+ */
+ private $formKey;
+
/**
* @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Catalog\Model\Session $catalogSession
@@ -128,6 +147,11 @@ class Toolbar extends \Magento\Framework\View\Element\Template
* @param ProductList $productListHelper
* @param \Magento\Framework\Data\Helper\PostHelper $postDataHelper
* @param array $data
+ * @param ToolbarMemorizer|null $toolbarMemorizer
+ * @param \Magento\Framework\App\Http\Context|null $httpContext
+ * @param \Magento\Framework\Data\Form\FormKey|null $formKey
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
@@ -137,7 +161,10 @@ public function __construct(
\Magento\Framework\Url\EncoderInterface $urlEncoder,
ProductList $productListHelper,
\Magento\Framework\Data\Helper\PostHelper $postDataHelper,
- array $data = []
+ array $data = [],
+ ToolbarMemorizer $toolbarMemorizer = null,
+ \Magento\Framework\App\Http\Context $httpContext = null,
+ \Magento\Framework\Data\Form\FormKey $formKey = null
) {
$this->_catalogSession = $catalogSession;
$this->_catalogConfig = $catalogConfig;
@@ -145,6 +172,15 @@ public function __construct(
$this->urlEncoder = $urlEncoder;
$this->_productListHelper = $productListHelper;
$this->_postDataHelper = $postDataHelper;
+ $this->toolbarMemorizer = $toolbarMemorizer ?: ObjectManager::getInstance()->get(
+ ToolbarMemorizer::class
+ );
+ $this->httpContext = $httpContext ?: ObjectManager::getInstance()->get(
+ \Magento\Framework\App\Http\Context::class
+ );
+ $this->formKey = $formKey ?: ObjectManager::getInstance()->get(
+ \Magento\Framework\Data\Form\FormKey::class
+ );
parent::__construct($context, $data);
}
@@ -152,6 +188,7 @@ public function __construct(
* Disable list state params memorizing
*
* @return $this
+ * @deprecated
*/
public function disableParamsMemorizing()
{
@@ -165,6 +202,7 @@ public function disableParamsMemorizing()
* @param string $param parameter name
* @param mixed $value parameter value
* @return $this
+ * @deprecated
*/
protected function _memorizeParam($param, $value)
{
@@ -244,13 +282,13 @@ public function getCurrentOrder()
$defaultOrder = $keys[0];
}
- $order = $this->_toolbarModel->getOrder();
+ $order = $this->toolbarMemorizer->getOrder();
if (!$order || !isset($orders[$order])) {
$order = $defaultOrder;
}
- if ($order != $defaultOrder) {
- $this->_memorizeParam('sort_order', $order);
+ if ($this->toolbarMemorizer->isMemorizingAllowed()) {
+ $this->httpContext->setValue(ToolbarModel::ORDER_PARAM_NAME, $order, $defaultOrder);
}
$this->setData('_current_grid_order', $order);
@@ -270,13 +308,13 @@ public function getCurrentDirection()
}
$directions = ['asc', 'desc'];
- $dir = strtolower($this->_toolbarModel->getDirection());
+ $dir = strtolower($this->toolbarMemorizer->getDirection());
if (!$dir || !in_array($dir, $directions)) {
$dir = $this->_direction;
}
- if ($dir != $this->_direction) {
- $this->_memorizeParam('sort_direction', $dir);
+ if ($this->toolbarMemorizer->isMemorizingAllowed()) {
+ $this->httpContext->setValue(ToolbarModel::DIRECTION_PARAM_NAME, $dir, $this->_direction);
}
$this->setData('_current_grid_direction', $dir);
@@ -392,6 +430,8 @@ public function getPagerUrl($params = [])
}
/**
+ * Get pager encoded url.
+ *
* @param array $params
* @return string
*/
@@ -412,11 +452,15 @@ public function getCurrentMode()
return $mode;
}
$defaultMode = $this->_productListHelper->getDefaultViewMode($this->getModes());
- $mode = $this->_toolbarModel->getMode();
+ $mode = $this->toolbarMemorizer->getMode();
if (!$mode || !isset($this->_availableMode[$mode])) {
$mode = $defaultMode;
}
+ if ($this->toolbarMemorizer->isMemorizingAllowed()) {
+ $this->httpContext->setValue(ToolbarModel::MODE_PARAM_NAME, $mode, $defaultMode);
+ }
+
$this->setData('_current_grid_mode', $mode);
return $mode;
}
@@ -568,13 +612,13 @@ public function getLimit()
$defaultLimit = $keys[0];
}
- $limit = $this->_toolbarModel->getLimit();
+ $limit = $this->toolbarMemorizer->getLimit();
if (!$limit || !isset($limits[$limit])) {
$limit = $defaultLimit;
}
- if ($limit != $defaultLimit) {
- $this->_memorizeParam('limit_page', $limit);
+ if ($this->toolbarMemorizer->isMemorizingAllowed()) {
+ $this->httpContext->setValue(ToolbarModel::LIMIT_PARAM_NAME, $limit, $defaultLimit);
}
$this->setData('_current_limit', $limit);
@@ -582,6 +626,8 @@ public function getLimit()
}
/**
+ * Check if limit is current used in toolbar.
+ *
* @param int $limit
* @return bool
*/
@@ -591,6 +637,8 @@ public function isLimitCurrent($limit)
}
/**
+ * Pager number of items from which products started on current page.
+ *
* @return int
*/
public function getFirstNum()
@@ -600,6 +648,8 @@ public function getFirstNum()
}
/**
+ * Pager number of items products finished on current page.
+ *
* @return int
*/
public function getLastNum()
@@ -609,6 +659,8 @@ public function getLastNum()
}
/**
+ * Total number of products in current category.
+ *
* @return int
*/
public function getTotalNum()
@@ -617,6 +669,8 @@ public function getTotalNum()
}
/**
+ * Check if current page is the first.
+ *
* @return bool
*/
public function isFirstPage()
@@ -625,6 +679,8 @@ public function isFirstPage()
}
/**
+ * Return last page number.
+ *
* @return int
*/
public function getLastPageNum()
@@ -692,6 +748,8 @@ public function getWidgetOptionsJson(array $customOptions = [])
'orderDefault' => $this->getOrderField(),
'limitDefault' => $this->_productListHelper->getDefaultLimitPerPageValue($defaultMode),
'url' => $this->getPagerUrl(),
+ 'formKey' => $this->formKey->getFormKey(),
+ 'post' => $this->toolbarMemorizer->isMemorizingAllowed() ? true : false
];
$options = array_replace_recursive($options, $customOptions);
return json_encode(['productListToolbarForm' => $options]);
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
index 0d64ecc9bff90..24822447ae915 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/Product/AddAttributeToTemplate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php
index bbef1de28e5b6..09eacbbf0731c 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 84ad6d2116726..49e601357c605 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
@@ -14,6 +14,7 @@
use Magento\Catalog\Controller\Adminhtml\Product\Attribute;
use Magento\Catalog\Helper\Product;
use Magento\Catalog\Model\Product\Attribute\Frontend\Inputtype\Presentation;
+use Magento\Framework\Serialize\Serializer\FormData;
use Magento\Catalog\Model\Product\AttributeSet\BuildFactory;
use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory;
use Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\Validator;
@@ -32,6 +33,8 @@
use Magento\Framework\View\Result\PageFactory;
/**
+ * Product attribute save controller.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Save extends Attribute implements HttpPostActionInterface
@@ -76,6 +79,11 @@ class Save extends Attribute implements HttpPostActionInterface
*/
private $presentation;
+ /**
+ * @var FormData|null
+ */
+ private $formDataSerializer;
+
/**
* @param Context $context
* @param FrontendInterface $attributeLabelCache
@@ -89,6 +97,7 @@ class Save extends Attribute implements HttpPostActionInterface
* @param Product $productHelper
* @param LayoutFactory $layoutFactory
* @param Presentation|null $presentation
+ * @param FormData|null $formDataSerializer
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -103,7 +112,8 @@ public function __construct(
FilterManager $filterManager,
Product $productHelper,
LayoutFactory $layoutFactory,
- Presentation $presentation = null
+ Presentation $presentation = null,
+ FormData $formDataSerializer = null
) {
parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory);
$this->buildFactory = $buildFactory;
@@ -114,19 +124,38 @@ public function __construct(
$this->groupCollectionFactory = $groupCollectionFactory;
$this->layoutFactory = $layoutFactory;
$this->presentation = $presentation ?: ObjectManager::getInstance()->get(Presentation::class);
+ $this->formDataSerializer = $formDataSerializer
+ ?: ObjectManager::getInstance()->get(FormData::class);
}
/**
+ * @inheritdoc
+ *
* @return Redirect
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @throws \Zend_Validate_Exception
*/
public function execute()
{
+ try {
+ $optionData = $this->formDataSerializer
+ ->unserialize($this->getRequest()->getParam('serialized_options', '[]'));
+ } catch (\InvalidArgumentException $e) {
+ $message = __("The attribute couldn't be saved due to an error. Verify your information and try again. "
+ . "If the error persists, please try again later.");
+ $this->messageManager->addErrorMessage($message);
+ return $this->returnResult('catalog/*/edit', ['_current' => true], ['error' => true]);
+ }
+
$data = $this->getRequest()->getPostValue();
+ $data = array_replace_recursive(
+ $data,
+ $optionData
+ );
+
if ($data) {
- $this->preprocessOptionsData($data);
$setId = $this->getRequest()->getParam('set');
$attributeSet = null;
@@ -135,7 +164,7 @@ public function execute()
$name = trim($name);
try {
- /** @var $attributeSet Set */
+ /** @var Set $attributeSet */
$attributeSet = $this->buildFactory->create()
->setEntityTypeId($this->_entityTypeId)
->setSkeletonId($setId)
@@ -157,7 +186,7 @@ public function execute()
$attributeId = $this->getRequest()->getParam('attribute_id');
- /** @var $model ProductAttributeInterface */
+ /** @var ProductAttributeInterface $model */
$model = $this->attributeFactory->create();
if ($attributeId) {
$model->load($attributeId);
@@ -189,7 +218,7 @@ public function execute()
//validate frontend_input
if (isset($data['frontend_input'])) {
- /** @var $inputType Validator */
+ /** @var Validator $inputType */
$inputType = $this->validatorFactory->create();
if (!$inputType->isValid($data['frontend_input'])) {
foreach ($inputType->getMessages() as $message) {
@@ -230,14 +259,14 @@ public function execute()
$data['backend_model'] = $this->productHelper->getAttributeBackendModelByInputType(
$data['frontend_input']
);
+
+ if ($model->getIsUserDefined() === null) {
+ $data['backend_type'] = $model->getBackendTypeByInput($data['frontend_input']);
+ }
}
$data += ['is_filterable' => 0, 'is_filterable_in_search' => 0];
- if ($model->getIsUserDefined() === null || $model->getIsUserDefined() != 0) {
- $data['backend_type'] = $model->getBackendTypeByInput($data['frontend_input']);
- }
-
$defaultValueField = $model->getDefaultValueByInput($data['frontend_input']);
if ($defaultValueField) {
$data['default_value'] = $this->getRequest()->getParam($defaultValueField);
@@ -317,28 +346,8 @@ public function execute()
}
/**
- * Extract options data from serialized options field and append to data array.
- *
- * This logic is required to overcome max_input_vars php limit
- * that may vary and/or be inaccessible to change on different instances.
+ * Provides an initialized Result object.
*
- * @param array $data
- * @return void
- */
- private function preprocessOptionsData(&$data)
- {
- if (isset($data['serialized_options'])) {
- $serializedOptions = json_decode($data['serialized_options'], JSON_OBJECT_AS_ARRAY);
- foreach ($serializedOptions as $serializedOption) {
- $option = [];
- parse_str($serializedOption, $option);
- $data = array_replace_recursive($data, $option);
- }
- }
- unset($data['serialized_options']);
- }
-
- /**
* @param string $path
* @param array $params
* @param array $response
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 dac92f9b2eb9e..381ca5d08d82a 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
@@ -7,11 +7,18 @@
namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
+use Magento\Framework\Serialize\Serializer\FormData;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\DataObject;
use Magento\Catalog\Controller\Adminhtml\Product\Attribute as AttributeAction;
+/**
+ * Product attribute validate controller.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class Validate extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface
{
const DEFAULT_MESSAGE_KEY = 'message';
@@ -31,6 +38,11 @@ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPo
*/
private $multipleAttributeList;
+ /**
+ * @var FormData|null
+ */
+ private $formDataSerializer;
+
/**
* Constructor
*
@@ -41,6 +53,7 @@ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPo
* @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
* @param \Magento\Framework\View\LayoutFactory $layoutFactory
* @param array $multipleAttributeList
+ * @param FormData|null $formDataSerializer
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
@@ -49,15 +62,20 @@ public function __construct(
\Magento\Framework\View\Result\PageFactory $resultPageFactory,
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
\Magento\Framework\View\LayoutFactory $layoutFactory,
- array $multipleAttributeList = []
+ array $multipleAttributeList = [],
+ FormData $formDataSerializer = null
) {
parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory);
$this->resultJsonFactory = $resultJsonFactory;
$this->layoutFactory = $layoutFactory;
$this->multipleAttributeList = $multipleAttributeList;
+ $this->formDataSerializer = $formDataSerializer ?: ObjectManager::getInstance()
+ ->get(FormData::class);
}
/**
+ * @inheritdoc
+ *
* @return \Magento\Framework\Controller\ResultInterface
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -66,6 +84,15 @@ public function execute()
{
$response = new DataObject();
$response->setError(false);
+ try {
+ $optionsData = $this->formDataSerializer
+ ->unserialize($this->getRequest()->getParam('serialized_options', '[]'));
+ } catch (\InvalidArgumentException $e) {
+ $message = __("The attribute couldn't be validated due to an error. Verify your information and try again. "
+ . "If the error persists, please try again later.");
+ $this->setMessageToResponse($response, [$message]);
+ $response->setError(true);
+ }
$attributeCode = $this->getRequest()->getParam('attribute_code');
$frontendLabel = $this->getRequest()->getParam('frontend_label');
@@ -78,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);
@@ -105,10 +132,10 @@ public function execute()
}
$multipleOption = $this->getRequest()->getParam("frontend_input");
- $multipleOption = null == $multipleOption ? 'select' : $multipleOption;
+ $multipleOption = (null === $multipleOption) ? 'select' : $multipleOption;
- if (isset($this->multipleAttributeList[$multipleOption]) && !(null == ($multipleOption))) {
- $options = $this->getRequest()->getParam($this->multipleAttributeList[$multipleOption]);
+ if (isset($this->multipleAttributeList[$multipleOption])) {
+ $options = $optionsData[$this->multipleAttributeList[$multipleOption]] ?? null;
$this->checkUniqueOption(
$response,
$options
@@ -126,7 +153,8 @@ public function execute()
}
/**
- * Throws Exception if not unique values into options
+ * Throws Exception if not unique values into options.
+ *
* @param array $optionsValues
* @param array $deletedOptions
* @return bool
@@ -160,6 +188,8 @@ private function setMessageToResponse($response, $messages)
}
/**
+ * Performs checking the uniqueness of the attribute options.
+ *
* @param DataObject $response
* @param array|null $options
* @return $this
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
index d82f4a04fb252..f11d16755ef0d 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
@@ -19,6 +19,8 @@
use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeFilter;
/**
+ * Product helper
+ *
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
@@ -365,6 +367,8 @@ private function overwriteValue($optionId, $option, $overwriteOptions)
}
/**
+ * Get link resolver instance
+ *
* @return LinkResolver
* @deprecated 101.0.0
*/
@@ -377,6 +381,8 @@ private function getLinkResolver()
}
/**
+ * Get DateTimeFilter instance
+ *
* @return \Magento\Framework\Stdlib\DateTime\Filter\DateTime
* @deprecated 101.0.0
*/
@@ -391,6 +397,7 @@ private function getDateTimeFilter()
/**
* Remove ids of non selected websites from $websiteIds array and return filtered data
+ *
* $websiteIds parameter expects array with website ids as keys and 1 (selected) or 0 (non selected) as values
* Only one id (default website ID) will be set to $websiteIds array when the single store mode is turned on
*
@@ -463,6 +470,7 @@ private function fillProductOptions(Product $product, array $productOptions)
private function convertSpecialFromDateStringToObject($productData)
{
if (isset($productData['special_from_date']) && $productData['special_from_date'] != '') {
+ $productData['special_from_date'] = $this->getDateTimeFilter()->filter($productData['special_from_date']);
$productData['special_from_date'] = new \DateTime($productData['special_from_date']);
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php
index b7655f7ee2862..9d7273fb3f23c 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php
@@ -14,6 +14,7 @@
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
/**
+ * Updates status for a batch of products.
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class MassStatus extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface
@@ -87,7 +88,7 @@ public function execute()
$filterRequest = $this->getRequest()->getParam('filters', null);
$status = (int) $this->getRequest()->getParam('status');
- if (null !== $storeId && null !== $filterRequest) {
+ if (null === $storeId && null !== $filterRequest) {
$storeId = (isset($filterRequest['store_id'])) ? (int) $filterRequest['store_id'] : 0;
}
diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php
index 19243aabb1b71..2088bb5ea77cd 100644
--- a/app/code/Magento/Catalog/Controller/Category/View.php
+++ b/app/code/Magento/Catalog/Controller/Category/View.php
@@ -10,6 +10,7 @@
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Model\Layer\Resolver;
+use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\App\Action\Action;
@@ -74,6 +75,11 @@ class View extends Action implements HttpGetActionInterface, HttpPostActionInter
*/
protected $categoryRepository;
+ /**
+ * @var ToolbarMemorizer
+ */
+ private $toolbarMemorizer;
+
/**
* Constructor
*
@@ -87,6 +93,7 @@ class View extends Action implements HttpGetActionInterface, HttpPostActionInter
* @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory
* @param Resolver $layerResolver
* @param CategoryRepositoryInterface $categoryRepository
+ * @param ToolbarMemorizer|null $toolbarMemorizer
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -99,7 +106,8 @@ public function __construct(
PageFactory $resultPageFactory,
\Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory,
Resolver $layerResolver,
- CategoryRepositoryInterface $categoryRepository
+ CategoryRepositoryInterface $categoryRepository,
+ ToolbarMemorizer $toolbarMemorizer = null
) {
parent::__construct($context);
$this->_storeManager = $storeManager;
@@ -111,6 +119,7 @@ public function __construct(
$this->resultForwardFactory = $resultForwardFactory;
$this->layerResolver = $layerResolver;
$this->categoryRepository = $categoryRepository;
+ $this->toolbarMemorizer = $toolbarMemorizer ?: $context->getObjectManager()->get(ToolbarMemorizer::class);
}
/**
@@ -135,6 +144,7 @@ protected function _initCategory()
}
$this->_catalogSession->setLastVisitedCategoryId($category->getId());
$this->_coreRegistry->register('current_category', $category);
+ $this->toolbarMemorizer->memorizeParams();
try {
$this->_eventManager->dispatch(
'catalog_controller_category_init_after',
@@ -198,7 +208,7 @@ public function execute()
if ($layoutUpdates && is_array($layoutUpdates)) {
foreach ($layoutUpdates as $layoutUpdate) {
$page->addUpdate($layoutUpdate);
- $page->addPageLayoutHandles(['layout_update' => md5($layoutUpdate)], null, false);
+ $page->addPageLayoutHandles(['layout_update' => sha1($layoutUpdate)], null, false);
}
}
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare.php b/app/code/Magento/Catalog/Controller/Product/Compare.php
index 1ee146e5aaa70..084a82f87d645 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/Helper/Data.php b/app/code/Magento/Catalog/Helper/Data.php
index ae20cda460796..3e96763632830 100644
--- a/app/code/Magento/Catalog/Helper/Data.php
+++ b/app/code/Magento/Catalog/Helper/Data.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Api\CategoryRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Store\Model\ScopeInterface;
use Magento\Customer\Model\Session as CustomerSession;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Pricing\PriceCurrencyInterface;
@@ -273,7 +274,8 @@ public function setStoreId($store)
/**
* Return current category path or get it from current category
- * and creating array of categories|product paths for breadcrumbs
+ *
+ * Creating array of categories|product paths for breadcrumbs
*
* @return array
*/
@@ -382,6 +384,7 @@ public function getLastViewedUrl()
/**
* Split SKU of an item by dashes and spaces
+ *
* Words will not be broken, unless this length is greater than $length
*
* @param string $sku
@@ -410,14 +413,15 @@ public function getAttributeHiddenFields()
/**
* Retrieve Catalog Price Scope
*
- * @return int
+ * @return int|null
*/
- public function getPriceScope()
+ public function getPriceScope(): ?int
{
- return $this->scopeConfig->getValue(
+ $priceScope = $this->scopeConfig->getValue(
self::XML_PATH_PRICE_SCOPE,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE
);
+ return isset($priceScope) ? (int)$priceScope : null;
}
/**
@@ -439,7 +443,7 @@ public function isUsingStaticUrlsAllowed()
{
return $this->scopeConfig->isSetFlag(
self::CONFIG_USE_STATIC_URLS,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE
);
}
@@ -454,7 +458,7 @@ public function isUrlDirectivesParsingAllowed()
{
return $this->scopeConfig->isSetFlag(
self::CONFIG_PARSE_URL_DIRECTIVES,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ ScopeInterface::SCOPE_STORE,
$this->_storeId
);
}
@@ -472,6 +476,7 @@ public function getPageTemplateProcessor()
/**
* Whether to display items count for each filter option
+ *
* @param int $storeId Store view ID
* @return bool
*/
@@ -479,12 +484,14 @@ public function shouldDisplayProductCountOnLayer($storeId = null)
{
return $this->scopeConfig->isSetFlag(
self::XML_PATH_DISPLAY_PRODUCT_COUNT,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ ScopeInterface::SCOPE_STORE,
$storeId
);
}
/**
+ * Convert tax address array to address data object with country id and postcode
+ *
* @param array $taxAddress
* @return \Magento\Customer\Api\Data\AddressInterface|null
*/
diff --git a/app/code/Magento/Catalog/Helper/Image.php b/app/code/Magento/Catalog/Helper/Image.php
index 758e59790d241..170f1209ad9e6 100644
--- a/app/code/Magento/Catalog/Helper/Image.php
+++ b/app/code/Magento/Catalog/Helper/Image.php
@@ -298,6 +298,7 @@ public function resize($width, $height = null)
*
* @param int $quality
* @return $this
+ * @deprecated
*/
public function setQuality($quality)
{
@@ -406,7 +407,8 @@ public function rotate($angle)
/**
* Add watermark to image
- * size param in format 100x200
+ *
+ * Size param in format 100x200
*
* @param string $fileName
* @param string $position
@@ -533,6 +535,8 @@ public function getUrl()
}
/**
+ * Save changes
+ *
* @return $this
*/
public function save()
@@ -553,6 +557,8 @@ public function getResizedImageInfo()
}
/**
+ * Getter for placeholder url
+ *
* @param null|string $placeholder
* @return string
*/
@@ -655,7 +661,8 @@ protected function getWatermarkPosition()
/**
* Set watermark size
- * param size in format 100x200
+ *
+ * Param size in format 100x200
*
* @param string $size
* @return $this
diff --git a/app/code/Magento/Catalog/Helper/Product/Compare.php b/app/code/Magento/Catalog/Helper/Product/Compare.php
index 90d98874e00cb..d6d35c5c76dd8 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/AbstractModel.php b/app/code/Magento/Catalog/Model/AbstractModel.php
index 007635b124331..78a49cd1e8b14 100644
--- a/app/code/Magento/Catalog/Model/AbstractModel.php
+++ b/app/code/Magento/Catalog/Model/AbstractModel.php
@@ -179,7 +179,7 @@ public function isLockedAttribute($attributeCode)
*
* @param string|array $key
* @param mixed $value
- * @return \Magento\Framework\DataObject
+ * @return $this
*/
public function setData($key, $value = null)
{
@@ -282,9 +282,9 @@ public function getWebsiteStoreIds()
*
* Default value existing is flag for using store value in data
*
- * @param string $attributeCode
- * @param mixed $value
- * @return $this
+ * @param string $attributeCode
+ * @param mixed $value
+ * @return $this
*
* @deprecated 101.0.0
*/
@@ -332,11 +332,10 @@ public function getAttributeDefaultValue($attributeCode)
}
/**
- * Set attribute code flag if attribute has value in current store and does not use
- * value of default store as value
+ * Set attribute code flag if attribute has value in current store and does not use value of default store as value
*
- * @param string $attributeCode
- * @return $this
+ * @param string $attributeCode
+ * @return $this
*
* @deprecated 101.0.0
*/
diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php
index f70bab73d0830..66a9132ae44b8 100644
--- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php
+++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php
@@ -38,6 +38,7 @@ class ProductCategoryCondition implements CustomConditionInterface
/**
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
+ * @param \Magento\Catalog\Model\CategoryRepository $categoryRepository
*/
public function __construct(
\Magento\Framework\App\ResourceConnection $resourceConnection,
@@ -104,7 +105,7 @@ private function getCategoryIds(Filter $filter): array
}
}
- return array_unique(array_merge($categoryIds, ...$childCategoryIds));
+ return array_map('intval', array_unique(array_merge($categoryIds, ...$childCategoryIds)));
}
/**
diff --git a/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php b/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php
index f22c6903a230c..4ea06d4e34d71 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 1e07c0cdd924e..44bf153f83697 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/Design.php b/app/code/Magento/Catalog/Model/Design.php
index bd7cdabb40856..853bbeac8eb38 100644
--- a/app/code/Magento/Catalog/Model/Design.php
+++ b/app/code/Magento/Catalog/Model/Design.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Model;
+use \Magento\Framework\TranslateInterface;
+
/**
* Catalog Custom Category design Model
*
@@ -31,14 +33,20 @@ class Design extends \Magento\Framework\Model\AbstractModel
*/
protected $_localeDate;
+ /**
+ * @var TranslateInterface
+ */
+ private $translator;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
* @param \Magento\Framework\View\DesignInterface $design
- * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
- * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
+ * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
+ * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
* @param array $data
+ * @param TranslateInterface|null $translator
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -47,10 +55,13 @@ public function __construct(
\Magento\Framework\View\DesignInterface $design,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ TranslateInterface $translator = null
) {
$this->_localeDate = $localeDate;
$this->_design = $design;
+ $this->translator = $translator ?:
+ \Magento\Framework\App\ObjectManager::getInstance()->get(TranslateInterface::class);
parent::__construct($context, $registry, $resource, $resourceCollection, $data);
}
@@ -63,6 +74,7 @@ public function __construct(
public function applyCustomDesign($design)
{
$this->_design->setDesignTheme($design);
+ $this->translator->loadData(null, true);
return $this;
}
diff --git a/app/code/Magento/Catalog/Model/FilterProductCustomAttribute.php b/app/code/Magento/Catalog/Model/FilterProductCustomAttribute.php
index b83bb97301b9c..497ed2fd49953 100644
--- a/app/code/Magento/Catalog/Model/FilterProductCustomAttribute.php
+++ b/app/code/Magento/Catalog/Model/FilterProductCustomAttribute.php
@@ -27,11 +27,12 @@ public function __construct(array $blackList = [])
/**
* Delete custom attribute
- * @param array $attributes
+ *
+ * @param array $attributes set objects attributes @example ['attribute_code'=>'attribute_object']
* @return array
*/
public function execute(array $attributes): array
{
- return array_diff($attributes, $this->blackList);
+ return array_diff_key($attributes, array_flip($this->blackList));
}
}
diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php
index ce92a2c1d958d..b5ca0895d6d1a 100644
--- a/app/code/Magento/Catalog/Model/ImageUploader.php
+++ b/app/code/Magento/Catalog/Model/ImageUploader.php
@@ -67,14 +67,9 @@ class ImageUploader
/**
* List of allowed image mime types
*
- * @var array
+ * @var string[]
*/
- private $allowedMimeTypes = [
- 'image/jpg',
- 'image/jpeg',
- 'image/gif',
- 'image/png',
- ];
+ private $allowedMimeTypes;
/**
* ImageUploader constructor
@@ -87,6 +82,7 @@ class ImageUploader
* @param string $baseTmpPath
* @param string $basePath
* @param string[] $allowedExtensions
+ * @param string[] $allowedMimeTypes
*/
public function __construct(
\Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase,
@@ -96,7 +92,8 @@ public function __construct(
\Psr\Log\LoggerInterface $logger,
$baseTmpPath,
$basePath,
- $allowedExtensions
+ $allowedExtensions,
+ $allowedMimeTypes = []
) {
$this->coreFileStorageDatabase = $coreFileStorageDatabase;
$this->mediaDirectory = $filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
@@ -106,6 +103,7 @@ public function __construct(
$this->baseTmpPath = $baseTmpPath;
$this->basePath = $basePath;
$this->allowedExtensions = $allowedExtensions;
+ $this->allowedMimeTypes = $allowedMimeTypes;
}
/**
@@ -165,7 +163,7 @@ public function getBasePath()
}
/**
- * Retrieve base path
+ * Retrieve allowed extensions
*
* @return string[]
*/
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php
index 64a8f930d83ee..a62e3d8f83b85 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Catalog\Model\Indexer\Category\Flat\Action;
+/**
+ * Class for full reindex flat categories
+ */
class Full extends \Magento\Catalog\Model\Indexer\Category\Flat\AbstractAction
{
/**
@@ -92,6 +95,7 @@ protected function populateFlatTables(array $stores)
/**
* Create table and add attributes as fields for specified store.
+ *
* This routine assumes that DDL operations are allowed
*
* @param int $store
@@ -109,6 +113,7 @@ protected function createTable($store)
/**
* Create category flat tables and add attributes as fields.
+ *
* Tables are created only if DDL operations are allowed
*
* @param \Magento\Store\Model\Store[] $stores if empty, create tables for all stores of the application
@@ -167,6 +172,44 @@ protected function switchTables(array $stores = [])
return $this;
}
+ /**
+ * Retrieve all actual Catalog Product Flat Table names
+ *
+ * @return string[]
+ */
+ private function getActualStoreTablesForCategoryFlat(): array
+ {
+ $actualStoreTables = [];
+ foreach ($this->storeManager->getStores() as $store) {
+ $actualStoreTables[] = sprintf(
+ '%s_store_%s',
+ $this->connection->getTableName('catalog_category_flat'),
+ $store->getId()
+ );
+ }
+
+ return $actualStoreTables;
+ }
+
+ /**
+ * Delete all category flat tables for not existing stores
+ *
+ * @return void
+ */
+ private function deleteAbandonedStoreCategoryFlatTables(): void
+ {
+ $existentTables = $this->connection->getTables(
+ $this->connection->getTableName('catalog_category_flat_store_%')
+ );
+ $actualStoreTables = $this->getActualStoreTablesForCategoryFlat();
+
+ $tablesToDelete = array_diff($existentTables, $actualStoreTables);
+
+ foreach ($tablesToDelete as $table) {
+ $this->connection->dropTable($table);
+ }
+ }
+
/**
* Transactional rebuild flat data from eav
*
@@ -182,7 +225,7 @@ public function reindexAll()
$stores = $this->storeManager->getStores();
$this->populateFlatTables($stores);
$this->switchTables($stores);
-
+ $this->deleteAbandonedStoreCategoryFlatTables();
$this->allowTableChanges = true;
return $this;
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php
index 6a499883ac723..178f4172ce6fa 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php
@@ -123,6 +123,11 @@ abstract class AbstractAction
*/
private $queryGenerator;
+ /**
+ * @var int
+ */
+ private $currentStoreId = 0;
+
/**
* @param ResourceConnection $resource
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -164,6 +169,7 @@ protected function reindex()
{
foreach ($this->storeManager->getStores() as $store) {
if ($this->getPathFromCategoryId($store->getRootCategoryId())) {
+ $this->currentStoreId = $store->getId();
$this->reindexRootCategory($store);
$this->reindexAnchorCategories($store);
$this->reindexNonAnchorCategories($store);
@@ -586,6 +592,8 @@ protected function createAnchorSelect(Store $store)
}
/**
+ * Get temporary table name
+ *
* Get temporary table name for concurrent indexing in persistent connection
* Temp table name is NOT shared between action instances and each action has it's own temp tree index
*
@@ -597,7 +605,7 @@ protected function getTemporaryTreeIndexTableName()
if (empty($this->tempTreeIndexTableName)) {
$this->tempTreeIndexTableName = $this->connection->getTableName('temp_catalog_category_tree_index')
. '_'
- . substr(md5(time() . random_int(0, 999999999)), 0, 8);
+ . substr(sha1(time() . random_int(0, 999999999)), 0, 8);
}
return $this->tempTreeIndexTableName;
@@ -641,7 +649,6 @@ protected function makeTempCategoryTreeIndex()
['child_id'],
['type' => \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_INDEX]
);
-
// Drop the temporary table in case it already exists on this (persistent?) connection.
$this->connection->dropTemporaryTable($temporaryName);
$this->connection->createTemporaryTable($temporaryTable);
@@ -659,11 +666,31 @@ protected function makeTempCategoryTreeIndex()
*/
protected function fillTempCategoryTreeIndex($temporaryName)
{
+ $isActiveAttributeId = $this->config->getAttribute(
+ \Magento\Catalog\Model\Category::ENTITY,
+ 'is_active'
+ )->getId();
+ $categoryMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\CategoryInterface::class);
+ $categoryLinkField = $categoryMetadata->getLinkField();
$selects = $this->prepareSelectsByRange(
$this->connection->select()
->from(
['c' => $this->getTable('catalog_category_entity')],
['entity_id', 'path']
+ )->joinInner(
+ ['ccacd' => $this->getTable('catalog_category_entity_int')],
+ 'ccacd.' . $categoryLinkField . ' = c.' . $categoryLinkField . ' AND ccacd.store_id = 0' .
+ ' AND ccacd.attribute_id = ' . $isActiveAttributeId,
+ []
+ )->joinLeft(
+ ['ccacs' => $this->getTable('catalog_category_entity_int')],
+ 'ccacs.' . $categoryLinkField . ' = c.' . $categoryLinkField
+ . ' AND ccacs.attribute_id = ccacd.attribute_id AND ccacs.store_id = ' .
+ $this->currentStoreId,
+ []
+ )->where(
+ $this->connection->getIfNullSql('ccacs.value', 'ccacd.value') . ' = ?',
+ 1
),
'entity_id'
);
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 6762602aecaf1..ad734b96d59d7 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 466ba746fa108..a669fb73f64fc 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 d9bfc96b7817a..64a7c4be4e03c 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 a32379b8c0a67..a3d958ea537e1 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.php b/app/code/Magento/Catalog/Model/Product.php
index 753fb0b5e18d7..44c5c891f4d6a 100644
--- a/app/code/Magento/Catalog/Model/Product.php
+++ b/app/code/Magento/Catalog/Model/Product.php
@@ -71,6 +71,11 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements
*/
const STORE_ID = 'store_id';
+ /**
+ * Product Url path.
+ */
+ const URL_PATH = 'url_path';
+
/**
* @var string
*/
@@ -507,13 +512,17 @@ protected function _getResource()
protected function getCustomAttributesCodes()
{
if ($this->customAttributesCodes === null) {
- $this->customAttributesCodes = array_keys($this->eavConfig->getEntityAttributes(
- self::ENTITY,
- $this
- ));
-
- $this->customAttributesCodes = $this->filterCustomAttribute->execute($this->customAttributesCodes);
- $this->customAttributesCodes = array_diff($this->customAttributesCodes, ProductInterface::ATTRIBUTES);
+ $this->customAttributesCodes = array_diff(
+ array_keys(
+ $this->filterCustomAttribute->execute(
+ $this->eavConfig->getEntityAttributes(
+ self::ENTITY,
+ $this
+ )
+ )
+ ),
+ ProductInterface::ATTRIBUTES
+ );
}
return $this->customAttributesCodes;
@@ -813,6 +822,9 @@ public function getStoreIds()
if (!$this->hasStoreIds()) {
$storeIds = [];
if ($websiteIds = $this->getWebsiteIds()) {
+ if ($this->_storeManager->isSingleStoreMode()) {
+ $websiteIds = array_keys($websiteIds);
+ }
foreach ($websiteIds as $websiteId) {
$websiteStores = $this->_storeManager->getWebsite($websiteId)->getStoreIds();
$storeIds = array_merge($storeIds, $websiteStores);
@@ -923,8 +935,8 @@ public function beforeSave()
*
* If value specified, it will be set.
*
- * @param bool $value
- * @return bool
+ * @param bool $value
+ * @return bool
*/
public function canAffectOptions($value = null)
{
@@ -1045,6 +1057,7 @@ public function reindex()
* Register indexing event before delete product
*
* @return \Magento\Catalog\Model\Product
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function beforeDelete()
{
@@ -1557,6 +1570,7 @@ public function hasGalleryAttribute()
* @param bool $move if true, it will move source file
* @param bool $exclude mark image as disabled in product page view
* @return \Magento\Catalog\Model\Product
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function addImageToMediaGallery($file, $mediaAttribute = null, $move = false, $exclude = true)
{
@@ -1718,8 +1732,6 @@ public function getIsSalable()
/**
* Check is a virtual product
*
- * Data helper wrapper
- *
* @return bool
*/
public function isVirtual()
@@ -1810,7 +1822,7 @@ public function formatUrlKey($str)
/**
* Save current attribute with code $code and assign new value
*
- * @param string $code Attribute code
+ * @param string $code Attribute code
* @param mixed $value New attribute value
* @param int $store Store ID
* @return void
@@ -2028,7 +2040,7 @@ public function getIsVirtual()
*
* @param string $code Option code
* @param mixed $value Value of the option
- * @param int|Product $product Product ID
+ * @param int|Product|null $product Product ID
* @return $this
*/
public function addCustomOption($code, $value, $product = null)
@@ -2560,7 +2572,7 @@ public function setTypeId($typeId)
/**
* @inheritdoc
*
- * @return \Magento\Catalog\Api\Data\ProductExtensionInterface
+ * @return \Magento\Framework\Api\ExtensionAttributesInterface
*/
public function getExtensionAttributes()
{
@@ -2581,10 +2593,11 @@ public function setExtensionAttributes(\Magento\Catalog\Api\Data\ProductExtensio
//@codeCoverageIgnoreEnd
/**
- * Convert array to media gallery interface
+ * Convert Image to ProductAttributeMediaGalleryEntryInterface
*
* @param array $mediaGallery
* @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface[]
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function convertToMediaGalleryInterface(array $mediaGallery)
{
@@ -2600,9 +2613,10 @@ protected function convertToMediaGalleryInterface(array $mediaGallery)
}
/**
- * Returns media gallery entries
+ * Get media gallery entries
*
* @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface[]|null
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getMediaGalleryEntries()
{
@@ -2620,6 +2634,7 @@ public function getMediaGalleryEntries()
*
* @param ProductAttributeMediaGalleryEntryInterface[] $mediaGalleryEntries
* @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function setMediaGalleryEntries(array $mediaGalleryEntries = null)
{
@@ -2660,7 +2675,7 @@ public function setId($value)
}
/**
- * Returns link repository instance
+ * Get link repository
*
* @return ProductLinkRepositoryInterface
*/
@@ -2674,7 +2689,7 @@ private function getLinkRepository()
}
/**
- * Returns media gallery processor instance
+ * Get media gallery processor
*
* @return Product\Gallery\Processor
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php
index a652d0ef90213..98738e055ca8f 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php
@@ -4,15 +4,13 @@
* See COPYING.txt for license details.
*/
-/**
- * Catalog product SKU backend attribute model
- *
- * @author Magento Core Team
- */
namespace Magento\Catalog\Model\Product\Attribute\Backend;
use Magento\Catalog\Model\Product;
+/**
+ * Catalog product SKU backend attribute model.
+ */
class Sku extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
{
/**
@@ -97,6 +95,7 @@ protected function _generateUniqueSku($object)
public function beforeSave($object)
{
$this->_generateUniqueSku($object);
+ $this->trimValue($object);
return parent::beforeSave($object);
}
@@ -127,4 +126,19 @@ protected function _getLastSimilarAttributeValueIncrement($attribute, $object)
$data = $connection->fetchOne($select, $bind);
return abs((int)str_replace($value, '', $data));
}
+
+ /**
+ * Remove extra spaces from attribute value before save.
+ *
+ * @param Product $object
+ * @return void
+ */
+ private function trimValue($object)
+ {
+ $attrCode = $this->getAttribute()->getAttributeCode();
+ $value = $object->getData($attrCode);
+ if ($value) {
+ $object->setData($attrCode, trim($value));
+ }
+ }
}
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 a112da79d16fa..aef3e87586015 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
@@ -86,14 +86,19 @@ public function execute($entity, $arguments = [])
__('Tier prices data should be array, but actually other type is received')
);
}
- $websiteId = $this->storeManager->getStore($entity->getStoreId())->getWebsiteId();
+ $websiteId = (int)$this->storeManager->getStore($entity->getStoreId())->getWebsiteId();
$isGlobal = $attribute->isScopeGlobal() || $websiteId === 0;
$identifierField = $this->metadataPoll->getMetadata(ProductInterface::class)->getLinkField();
- $productId = (int) $entity->getData($identifierField);
+ $productId = (int)$entity->getData($identifierField);
// prepare original data to compare
- $origPrices = $entity->getOrigData($attribute->getName());
- $old = $this->prepareOriginalDataToCompare($origPrices, $isGlobal);
+ $origPrices = [];
+ $originalId = $entity->getOrigData($identifierField);
+ if (empty($originalId) || $entity->getData($identifierField) == $originalId) {
+ $origPrices = $entity->getOrigData($attribute->getName());
+ }
+
+ $old = $this->prepareOldTierPriceToCompare($origPrices);
// prepare data for save
$new = $this->prepareNewDataForSave($priceRows, $isGlobal);
@@ -266,21 +271,18 @@ private function isWebsiteGlobal(int $websiteId): bool
}
/**
- * Prepare original data to compare.
+ * Prepare old data to compare.
*
* @param array|null $origPrices
- * @param bool $isGlobal
* @return array
*/
- private function prepareOriginalDataToCompare(?array $origPrices, bool $isGlobal = true): array
+ private function prepareOldTierPriceToCompare(?array $origPrices): array
{
$old = [];
if (is_array($origPrices)) {
foreach ($origPrices as $data) {
- if ($isGlobal === $this->isWebsiteGlobal((int)$data['website_id'])) {
- $key = $this->getPriceKey($data);
- $old[$key] = $data;
- }
+ $key = $this->getPriceKey($data);
+ $old[$key] = $data;
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
index 92b9a2e4239b2..e346c912dccaa 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
@@ -13,6 +13,9 @@
use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
+/**
+ * Backend model for Tierprice attribute
+ */
class Tierprice extends \Magento\Catalog\Model\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice
{
/**
@@ -159,8 +162,22 @@ protected function validatePrice(array $priceRow)
*/
protected function modifyPriceData($object, $data)
{
+ /** @var \Magento\Catalog\Model\Product $object */
$data = parent::modifyPriceData($object, $data);
$price = $object->getPrice();
+
+ $specialPrice = $object->getSpecialPrice();
+ $specialPriceFromDate = $object->getSpecialFromDate();
+ $specialPriceToDate = $object->getSpecialToDate();
+ $today = time();
+
+ if ($specialPrice && ($object->getPrice() > $object->getFinalPrice())) {
+ if ($today >= strtotime($specialPriceFromDate) && $today <= strtotime($specialPriceToDate) ||
+ $today >= strtotime($specialPriceFromDate) && $specialPriceToDate === null) {
+ $price = $specialPrice;
+ }
+ }
+
foreach ($data as $key => $tierPrice) {
$percentageValue = $this->getPercentage($tierPrice);
if ($percentageValue) {
@@ -172,6 +189,10 @@ protected function modifyPriceData($object, $data)
}
/**
+ * Update Price values in DB
+ *
+ * Updates price values in DB from array comparing to old values. Returns bool if updated
+ *
* @param array $valuesToUpdate
* @param array $oldValues
* @return boolean
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
index f6d3ca36c1e1e..99edfe5bc7208 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
@@ -11,6 +11,8 @@
use Magento\Framework\Exception\NoSuchEntityException;
/**
+ * Product attribute repository
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInterface
@@ -78,7 +80,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($attributeCode)
{
@@ -89,7 +91,7 @@ public function get($attributeCode)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
{
@@ -100,12 +102,17 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute)
{
+ $attribute->setEntityTypeId(
+ $this->eavConfig
+ ->getEntityType(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE)
+ ->getId()
+ );
if ($attribute->getAttributeId()) {
$existingModel = $this->get($attribute->getAttributeCode());
@@ -144,11 +151,6 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib
$attribute->setBackendModel(
$this->productHelper->getAttributeBackendModelByInputType($attribute->getFrontendInput())
);
- $attribute->setEntityTypeId(
- $this->eavConfig
- ->getEntityType(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE)
- ->getId()
- );
$attribute->setIsUserDefined(1);
}
if (!empty($attribute->getData(AttributeInterface::OPTIONS))) {
@@ -180,7 +182,7 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute)
{
@@ -189,7 +191,7 @@ public function delete(\Magento\Catalog\Api\Data\ProductAttributeInterface $attr
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteById($attributeCode)
{
@@ -200,7 +202,7 @@ public function deleteById($attributeCode)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getCustomAttributesMetadata($dataObjectClassName = null)
diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php
index e94104ae473a0..ce6b4d98bbc9f 100644
--- a/app/code/Magento/Catalog/Model/Product/Copier.php
+++ b/app/code/Magento/Catalog/Model/Product/Copier.php
@@ -8,7 +8,11 @@
namespace Magento\Catalog\Model\Product;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Product;
+/**
+ * The copier creates product duplicates.
+ */
class Copier
{
/**
@@ -49,7 +53,7 @@ public function __construct(
* @param \Magento\Catalog\Model\Product $product
* @return \Magento\Catalog\Model\Product
*/
- public function copy(\Magento\Catalog\Model\Product $product)
+ public function copy(Product $product)
{
$product->getWebsiteIds();
$product->getCategoryIds();
@@ -79,6 +83,7 @@ public function copy(\Magento\Catalog\Model\Product $product)
? $matches[1] . '-' . ($matches[2] + 1)
: $urlKey . '-1';
$duplicate->setUrlKey($urlKey);
+ $duplicate->setData(Product::URL_PATH, null);
try {
$duplicate->save();
$isDuplicateSaved = true;
@@ -94,6 +99,8 @@ public function copy(\Magento\Catalog\Model\Product $product)
}
/**
+ * Returns product option repository.
+ *
* @return Option\Repository
* @deprecated 101.0.0
*/
@@ -107,6 +114,8 @@ private function getOptionRepository()
}
/**
+ * Returns metadata pool.
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
* @deprecated 101.0.0
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
index 4d274a071d087..0e08b0af92862 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
@@ -1,6 +1,5 @@
contentValidator->isValid($entryContent)) {
throw new InputException(__('The image content is invalid. Verify the content and try again.'));
}
- $product = $this->productRepository->get($sku);
+ $product = $this->productRepository->get($sku, true);
$existingMediaGalleryEntries = $product->getMediaGalleryEntries();
$existingEntryIds = [];
@@ -84,11 +86,11 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function update($sku, ProductAttributeMediaGalleryEntryInterface $entry)
{
- $product = $this->productRepository->get($sku);
+ $product = $this->productRepository->get($sku, true);
$existingMediaGalleryEntries = $product->getMediaGalleryEntries();
if ($existingMediaGalleryEntries == null) {
throw new NoSuchEntityException(
@@ -125,11 +127,11 @@ public function update($sku, ProductAttributeMediaGalleryEntryInterface $entry)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function remove($sku, $entryId)
{
- $product = $this->productRepository->get($sku);
+ $product = $this->productRepository->get($sku, true);
$existingMediaGalleryEntries = $product->getMediaGalleryEntries();
if ($existingMediaGalleryEntries == null) {
throw new NoSuchEntityException(
@@ -155,7 +157,7 @@ public function remove($sku, $entryId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($sku, $entryId)
{
@@ -176,7 +178,7 @@ public function get($sku, $entryId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList($sku)
{
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php b/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php
index c6c7fbda7e9ec..0912324745360 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/Processor.php
@@ -6,9 +6,10 @@
namespace Magento\Catalog\Model\Product\Gallery;
+use Magento\Framework\Api\Data\ImageContentInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\Filesystem\DriverInterface;
+use Magento\Framework\App\ObjectManager;
/**
* Catalog product Media Gallery attribute processor.
@@ -56,28 +57,39 @@ class Processor
*/
protected $resourceModel;
+ /**
+ * @var \Magento\Framework\File\Mime
+ */
+ private $mime;
+
/**
* @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
* @param \Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb
* @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel
+ * @param \Magento\Framework\File\Mime|null $mime
+ * @throws \Magento\Framework\Exception\FileSystemException
*/
public function __construct(
\Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
\Magento\MediaStorage\Helper\File\Storage\Database $fileStorageDb,
\Magento\Catalog\Model\Product\Media\Config $mediaConfig,
\Magento\Framework\Filesystem $filesystem,
- \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel
+ \Magento\Catalog\Model\ResourceModel\Product\Gallery $resourceModel,
+ \Magento\Framework\File\Mime $mime = null
) {
$this->attributeRepository = $attributeRepository;
$this->fileStorageDb = $fileStorageDb;
$this->mediaConfig = $mediaConfig;
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->resourceModel = $resourceModel;
+ $this->mime = $mime ?: ObjectManager::getInstance()->get(\Magento\Framework\File\Mime::class);
}
/**
+ * Return media_gallery attribute
+ *
* @return \Magento\Catalog\Api\Data\ProductAttributeInterface
* @since 101.0.0
*/
@@ -183,6 +195,13 @@ public function addImage(
$attrCode = $this->getAttribute()->getAttributeCode();
$mediaGalleryData = $product->getData($attrCode);
$position = 0;
+
+ $absoluteFilePath = $this->mediaDirectory->getAbsolutePath($file);
+ $imageMimeType = $this->mime->getMimeType($absoluteFilePath);
+ $imageContent = $this->mediaDirectory->readFile($absoluteFilePath);
+ $imageBase64 = base64_encode($imageContent);
+ $imageName = $pathinfo['filename'];
+
if (!is_array($mediaGalleryData)) {
$mediaGalleryData = ['images' => []];
}
@@ -197,9 +216,17 @@ public function addImage(
$mediaGalleryData['images'][] = [
'file' => $fileName,
'position' => $position,
- 'media_type' => 'image',
'label' => '',
'disabled' => (int)$exclude,
+ 'media_type' => 'image',
+ 'types' => $mediaAttribute,
+ 'content' => [
+ 'data' => [
+ ImageContentInterface::NAME => $imageName,
+ ImageContentInterface::BASE64_ENCODED_DATA => $imageBase64,
+ ImageContentInterface::TYPE => $imageMimeType,
+ ]
+ ]
];
$product->setData($attrCode, $mediaGalleryData);
@@ -358,7 +385,8 @@ public function setMediaAttribute(\Magento\Catalog\Model\Product $product, $medi
}
/**
- * get media attribute codes
+ * Get media attribute codes
+ *
* @return array
* @since 101.0.0
*/
@@ -368,6 +396,8 @@ public function getMediaAttributeCodes()
}
/**
+ * Trim .tmp ending from filename
+ *
* @param string $file
* @return string
* @since 101.0.0
@@ -489,7 +519,6 @@ public function getAffectedFields($object)
/**
* Attribute value is not to be saved in a conventional way, separate table is used to store the complex value
*
- * {@inheritdoc}
* @since 101.0.0
*/
public function isScalar()
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/ReadHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/ReadHandler.php
index c785d08e64b7f..a3726207b3024 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/ReadHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/ReadHandler.php
@@ -47,6 +47,8 @@ public function __construct(
}
/**
+ * Execute read handler for catalog product gallery
+ *
* @param Product $entity
* @param array $arguments
* @return object
@@ -55,9 +57,6 @@ public function __construct(
*/
public function execute($entity, $arguments = [])
{
- $value = [];
- $value['images'] = [];
-
$mediaEntries = $this->resourceModel->loadProductGalleryByAttributeId(
$entity,
$this->getAttribute()->getAttributeId()
@@ -72,6 +71,8 @@ public function execute($entity, $arguments = [])
}
/**
+ * Add media data to product
+ *
* @param Product $product
* @param array $mediaEntries
* @return void
@@ -79,40 +80,18 @@ public function execute($entity, $arguments = [])
*/
public function addMediaDataToProduct(Product $product, array $mediaEntries)
{
- $attrCode = $this->getAttribute()->getAttributeCode();
- $value = [];
- $value['images'] = [];
- $value['values'] = [];
-
- foreach ($mediaEntries as $mediaEntry) {
- $mediaEntry = $this->substituteNullsWithDefaultValues($mediaEntry);
- $value['images'][$mediaEntry['value_id']] = $mediaEntry;
- }
- $product->setData($attrCode, $value);
- }
-
- /**
- * @param array $rawData
- * @return array
- */
- private function substituteNullsWithDefaultValues(array $rawData)
- {
- $processedData = [];
- foreach ($rawData as $key => $rawValue) {
- if (null !== $rawValue) {
- $processedValue = $rawValue;
- } elseif (isset($rawData[$key . '_default'])) {
- $processedValue = $rawData[$key . '_default'];
- } else {
- $processedValue = null;
- }
- $processedData[$key] = $processedValue;
- }
-
- return $processedData;
+ $product->setData(
+ $this->getAttribute()->getAttributeCode(),
+ [
+ 'images' => array_column($mediaEntries, null, 'value_id'),
+ 'values' => []
+ ]
+ );
}
/**
+ * Get attribute
+ *
* @return \Magento\Catalog\Api\Data\ProductAttributeInterface
* @since 101.0.0
*/
@@ -126,6 +105,8 @@ public function getAttribute()
}
/**
+ * Find default value
+ *
* @param string $key
* @param string[] &$image
* @return string
diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php
index f1ae9ac62dc9b..a0be36c5a327c 100644
--- a/app/code/Magento/Catalog/Model/Product/Image.php
+++ b/app/code/Magento/Catalog/Model/Product/Image.php
@@ -15,6 +15,8 @@
use Magento\Catalog\Model\Product\Image\ParamsBuilder;
/**
+ * Image operations
+ *
* @method string getFile()
* @method string getLabel()
* @method string getPosition()
@@ -24,6 +26,11 @@
*/
class Image extends \Magento\Framework\Model\AbstractModel
{
+ /**
+ * Config path for the jpeg image quality value
+ */
+ const XML_PATH_JPEG_QUALITY = 'system/upload_configuration/jpeg_quality';
+
/**
* @var int
*/
@@ -38,8 +45,9 @@ class Image extends \Magento\Framework\Model\AbstractModel
* Default quality value (for JPEG images only).
*
* @var int
+ * @deprecated use config setting with path self::XML_PATH_JPEG_QUALITY
*/
- protected $_quality = 80;
+ protected $_quality = null;
/**
* @var bool
@@ -203,13 +211,13 @@ class Image extends \Magento\Framework\Model\AbstractModel
* @param \Magento\Framework\Image\Factory $imageFactory
* @param \Magento\Framework\View\Asset\Repository $assetRepo
* @param \Magento\Framework\View\FileSystem $viewFileSystem
+ * @param ImageFactory $viewAssetImageFactory
+ * @param PlaceholderFactory $viewAssetPlaceholderFactory
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
- * @param ImageFactory|null $viewAssetImageFactory
- * @param PlaceholderFactory|null $viewAssetPlaceholderFactory
- * @param SerializerInterface|null $serializer
+ * @param SerializerInterface $serializer
* @param ParamsBuilder $paramsBuilder
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
@@ -249,6 +257,8 @@ public function __construct(
}
/**
+ * Set image width property
+ *
* @param int $width
* @return $this
*/
@@ -259,6 +269,8 @@ public function setWidth($width)
}
/**
+ * Get image width property
+ *
* @return int
*/
public function getWidth()
@@ -267,6 +279,8 @@ public function getWidth()
}
/**
+ * Set image height property
+ *
* @param int $height
* @return $this
*/
@@ -277,6 +291,8 @@ public function setHeight($height)
}
/**
+ * Get image height property
+ *
* @return int
*/
public function getHeight()
@@ -289,6 +305,7 @@ public function getHeight()
*
* @param int $quality
* @return $this
+ * @deprecated use config setting with path self::XML_PATH_JPEG_QUALITY
*/
public function setQuality($quality)
{
@@ -303,10 +320,14 @@ public function setQuality($quality)
*/
public function getQuality()
{
- return $this->_quality;
+ return $this->_quality === null
+ ? $this->_scopeConfig->getValue(self::XML_PATH_JPEG_QUALITY)
+ : $this->_quality;
}
/**
+ * Set _keepAspectRatio property
+ *
* @param bool $keep
* @return $this
*/
@@ -317,6 +338,8 @@ public function setKeepAspectRatio($keep)
}
/**
+ * Set _keepFrame property
+ *
* @param bool $keep
* @return $this
*/
@@ -327,6 +350,8 @@ public function setKeepFrame($keep)
}
/**
+ * Set _keepTransparency
+ *
* @param bool $keep
* @return $this
*/
@@ -337,6 +362,8 @@ public function setKeepTransparency($keep)
}
/**
+ * Set _constrainOnly
+ *
* @param bool $flag
* @return $this
*/
@@ -347,6 +374,8 @@ public function setConstrainOnly($flag)
}
/**
+ * Set background color
+ *
* @param int[] $rgbArray
* @return $this
*/
@@ -357,6 +386,8 @@ public function setBackgroundColor(array $rgbArray)
}
/**
+ * Set size
+ *
* @param string $size
* @return $this
*/
@@ -411,6 +442,8 @@ public function setBaseFile($file)
}
/**
+ * Get base filename
+ *
* @return string
*/
public function getBaseFile()
@@ -419,6 +452,8 @@ public function getBaseFile()
}
/**
+ * Get new file
+ *
* @deprecated 101.1.0
* @return bool|string
*/
@@ -438,6 +473,8 @@ public function isBaseFilePlaceholder()
}
/**
+ * Set image processor
+ *
* @param MagentoImage $processor
* @return $this
*/
@@ -448,6 +485,8 @@ public function setImageProcessor($processor)
}
/**
+ * Get image processor
+ *
* @return MagentoImage
*/
public function getImageProcessor()
@@ -461,11 +500,13 @@ public function getImageProcessor()
$this->_processor->keepTransparency($this->_keepTransparency);
$this->_processor->constrainOnly($this->_constrainOnly);
$this->_processor->backgroundColor($this->_backgroundColor);
- $this->_processor->quality($this->_quality);
+ $this->_processor->quality($this->getQuality());
return $this->_processor;
}
/**
+ * Resize image
+ *
* @see \Magento\Framework\Image\Adapter\AbstractAdapter
* @return $this
*/
@@ -479,6 +520,8 @@ public function resize()
}
/**
+ * Rotate image
+ *
* @param int $angle
* @return $this
*/
@@ -505,7 +548,8 @@ public function setAngle($angle)
/**
* Add watermark to image
- * size param in format 100x200
+ *
+ * Size param in format 100x200
*
* @param string $file
* @param string $position
@@ -564,6 +608,8 @@ public function setWatermark(
}
/**
+ * Save file
+ *
* @return $this
*/
public function saveFile()
@@ -578,6 +624,8 @@ public function saveFile()
}
/**
+ * Get url
+ *
* @return string
*/
public function getUrl()
@@ -586,6 +634,8 @@ public function getUrl()
}
/**
+ * Set destination subdir
+ *
* @param string $dir
* @return $this
*/
@@ -596,6 +646,8 @@ public function setDestinationSubdir($dir)
}
/**
+ * Get destination subdir
+ *
* @return string
*/
public function getDestinationSubdir()
@@ -604,6 +656,8 @@ public function getDestinationSubdir()
}
/**
+ * Check is image cached
+ *
* @return bool
*/
public function isCached()
@@ -636,7 +690,8 @@ public function getWatermarkFile()
/**
* Get relative watermark file path
- * or false if file not found
+ *
+ * Return false if file not found
*
* @return string | bool
*/
@@ -771,7 +826,10 @@ public function getWatermarkHeight()
}
/**
+ * Clear cache
+ *
* @return void
+ * @throws \Magento\Framework\Exception\FileSystemException
*/
public function clearCache()
{
@@ -784,6 +842,7 @@ public function clearCache()
/**
* First check this file on FS
+ *
* If it doesn't exist - try to download it from DB
*
* @param string $filename
@@ -802,6 +861,7 @@ protected function _fileExists($filename)
/**
* Return resized product image information
+ *
* @return array
* @throws NotLoadInfoImageException
*/
@@ -843,7 +903,7 @@ private function getMiscParams()
'transparency' => $this->_keepTransparency,
'background' => $this->_backgroundColor,
'angle' => $this->_angle,
- 'quality' => $this->_quality
+ 'quality' => $this->getQuality()
]
);
}
diff --git a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
index dd8d352fecebc..f6be7f7392b5e 100644
--- a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
+++ b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
@@ -10,17 +10,13 @@
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\View\ConfigInterface;
use Magento\Store\Model\ScopeInterface;
+use Magento\Catalog\Model\Product\Image;
/**
* Builds parameters array used to build Image Asset
*/
class ParamsBuilder
{
- /**
- * @var int
- */
- private $defaultQuality = 80;
-
/**
* @var array
*/
@@ -69,6 +65,8 @@ public function __construct(
}
/**
+ * Build image params
+ *
* @param array $imageArguments
* @return array
* @SuppressWarnings(PHPMD.NPathComplexity)
@@ -89,6 +87,8 @@ public function build(array $imageArguments): array
}
/**
+ * Overwrite default values
+ *
* @param array $imageArguments
* @return array
*/
@@ -100,11 +100,12 @@ private function overwriteDefaultValues(array $imageArguments): array
$transparency = $imageArguments['transparency'] ?? $this->defaultKeepTransparency;
$background = $imageArguments['background'] ?? $this->defaultBackground;
$angle = $imageArguments['angle'] ?? $this->defaultAngle;
+ $quality = (int) $this->scopeConfig->getValue(Image::XML_PATH_JPEG_QUALITY);
return [
'background' => (array) $background,
'angle' => $angle,
- 'quality' => $this->defaultQuality,
+ 'quality' => $quality,
'keep_aspect_ratio' => (bool) $aspectRatio,
'keep_frame' => (bool) $frame,
'keep_transparency' => (bool) $transparency,
@@ -113,6 +114,8 @@ private function overwriteDefaultValues(array $imageArguments): array
}
/**
+ * Get watermark
+ *
* @param string $type
* @return array
*/
@@ -153,6 +156,7 @@ private function getWatermark(string $type): array
/**
* Get frame from product_image_white_borders
+ *
* @return bool
*/
private function hasDefaultFrame(): bool
diff --git a/app/code/Magento/Catalog/Model/Product/Media/Config.php b/app/code/Magento/Catalog/Model/Product/Media/Config.php
index 72936d317399c..33af93db13b4c 100644
--- a/app/code/Magento/Catalog/Model/Product/Media/Config.php
+++ b/app/code/Magento/Catalog/Model/Product/Media/Config.php
@@ -10,11 +10,9 @@
use Magento\Store\Model\StoreManagerInterface;
/**
- * Catalog product media config
+ * Catalog product media config.
*
* @api
- *
- * @author Magento Core Team
* @since 100.0.2
*/
class Config implements ConfigInterface
@@ -31,6 +29,11 @@ class Config implements ConfigInterface
*/
private $attributeHelper;
+ /**
+ * @var string[]
+ */
+ private $mediaAttributeCodes;
+
/**
* @param StoreManagerInterface $storeManager
*/
@@ -40,8 +43,7 @@ public function __construct(StoreManagerInterface $storeManager)
}
/**
- * Filesystem directory path of product images
- * relatively to media folder
+ * Get filesystem directory path for product images relative to the media directory.
*
* @return string
*/
@@ -51,8 +53,7 @@ public function getBaseMediaPathAddition()
}
/**
- * Web-based directory path of product images
- * relatively to media folder
+ * Get web-based directory path for product images relative to the media directory.
*
* @return string
*/
@@ -62,7 +63,7 @@ public function getBaseMediaUrlAddition()
}
/**
- * @return string
+ * @inheritdoc
*/
public function getBaseMediaPath()
{
@@ -70,7 +71,7 @@ public function getBaseMediaPath()
}
/**
- * @return string
+ * @inheritdoc
*/
public function getBaseMediaUrl()
{
@@ -79,8 +80,7 @@ public function getBaseMediaUrl()
}
/**
- * Filesystem directory path of temporary product images
- * relatively to media folder
+ * Filesystem directory path of temporary product images relative to the media directory.
*
* @return string
*/
@@ -90,6 +90,8 @@ public function getBaseTmpMediaPath()
}
/**
+ * Get temporary base media URL.
+ *
* @return string
*/
public function getBaseTmpMediaUrl()
@@ -100,8 +102,7 @@ public function getBaseTmpMediaUrl()
}
/**
- * @param string $file
- * @return string
+ * @inheritdoc
*/
public function getMediaUrl($file)
{
@@ -109,8 +110,7 @@ public function getMediaUrl($file)
}
/**
- * @param string $file
- * @return string
+ * @inheritdoc
*/
public function getMediaPath($file)
{
@@ -118,6 +118,8 @@ public function getMediaPath($file)
}
/**
+ * Get temporary media URL.
+ *
* @param string $file
* @return string
*/
@@ -127,8 +129,7 @@ public function getTmpMediaUrl($file)
}
/**
- * Part of URL of temporary product images
- * relatively to media folder
+ * Part of URL of temporary product images relative to the media directory.
*
* @param string $file
* @return string
@@ -139,7 +140,7 @@ public function getTmpMediaShortUrl($file)
}
/**
- * Part of URL of product images relatively to media folder
+ * Part of URL of product images relatively to media folder.
*
* @param string $file
* @return string
@@ -150,6 +151,8 @@ public function getMediaShortUrl($file)
}
/**
+ * Get path to the temporary media.
+ *
* @param string $file
* @return string
*/
@@ -159,6 +162,8 @@ public function getTmpMediaPath($file)
}
/**
+ * Process file path.
+ *
* @param string $file
* @return string
*/
@@ -168,15 +173,23 @@ protected function _prepareFile($file)
}
/**
+ * Get codes of media attribute.
+ *
* @return array
* @since 100.0.4
*/
public function getMediaAttributeCodes()
{
- return $this->getAttributeHelper()->getAttributeCodesByFrontendType('media_image');
+ if (!isset($this->mediaAttributeCodes)) {
+ // the in-memory object-level caching allows to prevent unnecessary calls to the DB
+ $this->mediaAttributeCodes = $this->getAttributeHelper()->getAttributeCodesByFrontendType('media_image');
+ }
+ return $this->mediaAttributeCodes;
}
/**
+ * Get attribute helper.
+ *
* @return Attribute
*/
private function getAttributeHelper()
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php
new file mode 100644
index 0000000000000..c9afdf023b307
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php
@@ -0,0 +1,52 @@
+_messages = [];
+ $this->_errors = [];
+
+ if (!is_string($value)) {
+ $this->_messages[] = __('Full file path is expected.')->render();
+ return false;
+ }
+
+ $result = true;
+ $fileInfo = null;
+ if ($originalName) {
+ $fileInfo = ['name' => $originalName];
+ }
+ foreach ($this->_validators as $element) {
+ $validator = $element['instance'];
+ if ($validator->isValid($value, $fileInfo)) {
+ continue;
+ }
+ $result = false;
+ $messages = $validator->getMessages();
+ $this->_messages = array_merge($this->_messages, $messages);
+ $this->_errors = array_merge($this->_errors, array_keys($messages));
+ if ($element['breakChainOnFailure']) {
+ break;
+ }
+ }
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php
index 32c901afe8e74..a7add0ad87b89 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php
@@ -6,13 +6,18 @@
namespace Magento\Catalog\Model\Product\Option\Type\File;
+/**
+ * Class ValidateFactory. Creates Validator with type "ExistingValidate"
+ */
class ValidateFactory
{
/**
+ * Main factory method
+ *
* @return \Zend_Validate
*/
public function create()
{
- return new \Zend_Validate();
+ return new ExistingValidate();
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
index d6a5cb1cbc29d..fef4999a1174a 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
@@ -10,8 +10,12 @@
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Catalog\Model\Product\Exception as ProductException;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Math\Random;
+use Magento\Framework\App\ObjectManager;
/**
+ * Validator class. Represents logic for validation file given from product option
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ValidatorFile extends Validator
@@ -63,11 +67,19 @@ class ValidatorFile extends Validator
protected $isImageValidator;
/**
+ * @var Random
+ */
+ private $random;
+
+ /**
+ * Constructor method
+ *
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\Framework\File\Size $fileSize
* @param \Magento\Framework\HTTP\Adapter\FileTransferFactory $httpFactory
* @param \Magento\Framework\Validator\File\IsImage $isImageValidator
+ * @param Random|null $random
* @throws \Magento\Framework\Exception\FileSystemException
*/
public function __construct(
@@ -75,16 +87,21 @@ public function __construct(
\Magento\Framework\Filesystem $filesystem,
\Magento\Framework\File\Size $fileSize,
\Magento\Framework\HTTP\Adapter\FileTransferFactory $httpFactory,
- \Magento\Framework\Validator\File\IsImage $isImageValidator
+ \Magento\Framework\Validator\File\IsImage $isImageValidator,
+ Random $random = null
) {
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->filesystem = $filesystem;
$this->httpFactory = $httpFactory;
$this->isImageValidator = $isImageValidator;
+ $this->random = $random
+ ?? ObjectManager::getInstance()->get(Random::class);
parent::__construct($scopeConfig, $filesystem, $fileSize);
}
/**
+ * Setter method for the product
+ *
* @param Product $product
* @return $this
*/
@@ -95,6 +112,8 @@ public function setProduct(Product $product)
}
/**
+ * Validation method
+ *
* @param \Magento\Framework\DataObject $processingParams
* @param \Magento\Catalog\Model\Product\Option $option
* @return array
@@ -154,8 +173,6 @@ public function validate($processingParams, $option)
$userValue = [];
if ($upload->isUploaded($file) && $upload->isValid($file)) {
- $extension = pathinfo(strtolower($fileInfo['name']), PATHINFO_EXTENSION);
-
$fileName = \Magento\MediaStorage\Model\File\Uploader::getCorrectFileName($fileInfo['name']);
$dispersion = \Magento\MediaStorage\Model\File\Uploader::getDispersionPath($fileName);
@@ -163,7 +180,8 @@ public function validate($processingParams, $option)
$tmpDirectory = $this->filesystem->getDirectoryRead(DirectoryList::SYS_TMP);
$fileHash = md5($tmpDirectory->readFile($tmpDirectory->getRelativePath($fileInfo['tmp_name'])));
- $filePath .= '/' . $fileHash . '.' . $extension;
+ $fileRandomName = $this->random->getRandomString(32);
+ $filePath .= '/' .$fileRandomName;
$fileFullPath = $this->mediaDirectory->getAbsolutePath($this->quotePath . $filePath);
$upload->addFilter(new \Zend_Filter_File_Rename(['target' => $fileFullPath, 'overwrite' => true]));
@@ -243,6 +261,8 @@ protected function initFilesystem()
}
/**
+ * Validate contents length method
+ *
* @return bool
* @todo need correctly name
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
index 37e4c7b310a81..100ad37273cff 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
@@ -6,6 +6,9 @@
namespace Magento\Catalog\Model\Product\Option\Type\File;
+/**
+ * Validator for existing files.
+ */
class ValidatorInfo extends Validator
{
/**
@@ -34,6 +37,8 @@ class ValidatorInfo extends Validator
protected $fileRelativePath;
/**
+ * Construct method
+ *
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\Framework\File\Size $fileSize
@@ -53,6 +58,8 @@ public function __construct(
}
/**
+ * Setter method for property "useQuotePath"
+ *
* @param mixed $useQuotePath
* @return $this
*/
@@ -63,6 +70,8 @@ public function setUseQuotePath($useQuotePath)
}
/**
+ * Validate method for the option value depends on an option
+ *
* @param array $optionValue
* @param \Magento\Catalog\Model\Product\Option $option
* @return bool
@@ -90,7 +99,7 @@ public function validate($optionValue, $option)
}
$result = false;
- if ($validatorChain->isValid($this->fileFullPath)) {
+ if ($validatorChain->isValid($this->fileFullPath, $optionValue['title'])) {
$result = $this->rootDirectory->isReadable($this->fileRelativePath)
&& isset($optionValue['secret_key'])
&& $this->buildSecretKey($this->fileRelativePath) == $optionValue['secret_key'];
@@ -109,6 +118,8 @@ public function validate($optionValue, $option)
}
/**
+ * Method for creation secret key for the given file
+ *
* @param string $fileRelativePath
* @return string
*/
@@ -118,6 +129,8 @@ protected function buildSecretKey($fileRelativePath)
}
/**
+ * Calculates path for the file
+ *
* @param array $optionValue
* @return void
*/
diff --git a/app/code/Magento/Catalog/Model/Product/ProductList/ToolbarMemorizer.php b/app/code/Magento/Catalog/Model/Product/ProductList/ToolbarMemorizer.php
new file mode 100644
index 0000000000000..9c1a781d594f7
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/ProductList/ToolbarMemorizer.php
@@ -0,0 +1,179 @@
+toolbarModel = $toolbarModel;
+ $this->catalogSession = $catalogSession;
+ $this->scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Get sort order
+ *
+ * @return string|bool
+ */
+ public function getOrder()
+ {
+ if ($this->order === null) {
+ $this->order = $this->toolbarModel->getOrder() ??
+ ($this->isMemorizingAllowed() ? $this->catalogSession->getData(Toolbar::ORDER_PARAM_NAME) : null);
+ }
+ return $this->order;
+ }
+
+ /**
+ * Get sort direction
+ *
+ * @return string|bool
+ */
+ public function getDirection()
+ {
+ if ($this->direction === null) {
+ $this->direction = $this->toolbarModel->getDirection() ??
+ ($this->isMemorizingAllowed() ? $this->catalogSession->getData(Toolbar::DIRECTION_PARAM_NAME) : null);
+ }
+ return $this->direction;
+ }
+
+ /**
+ * Get sort mode
+ *
+ * @return string|bool
+ */
+ public function getMode()
+ {
+ if ($this->mode === null) {
+ $this->mode = $this->toolbarModel->getMode() ??
+ ($this->isMemorizingAllowed() ? $this->catalogSession->getData(Toolbar::MODE_PARAM_NAME) : null);
+ }
+ return $this->mode;
+ }
+
+ /**
+ * Get products per page limit
+ *
+ * @return string|bool
+ */
+ public function getLimit()
+ {
+ if ($this->limit === null) {
+ $this->limit = $this->toolbarModel->getLimit() ??
+ ($this->isMemorizingAllowed() ? $this->catalogSession->getData(Toolbar::LIMIT_PARAM_NAME) : null);
+ }
+ return $this->limit;
+ }
+
+ /**
+ * Method to save all catalog parameters in catalog session
+ *
+ * @return void
+ */
+ public function memorizeParams()
+ {
+ if (!$this->catalogSession->getParamsMemorizeDisabled() && $this->isMemorizingAllowed()) {
+ $this->memorizeParam(Toolbar::ORDER_PARAM_NAME, $this->getOrder())
+ ->memorizeParam(Toolbar::DIRECTION_PARAM_NAME, $this->getDirection())
+ ->memorizeParam(Toolbar::MODE_PARAM_NAME, $this->getMode())
+ ->memorizeParam(Toolbar::LIMIT_PARAM_NAME, $this->getLimit());
+ }
+ }
+
+ /**
+ * Check configuration for enabled/disabled toolbar memorizing
+ *
+ * @return bool
+ */
+ public function isMemorizingAllowed()
+ {
+ if ($this->isMemorizingAllowed === null) {
+ $this->isMemorizingAllowed = $this->scopeConfig->isSetFlag(self::XML_PATH_CATALOG_REMEMBER_PAGINATION);
+ }
+ return $this->isMemorizingAllowed;
+ }
+
+ /**
+ * Memorize parameter value for session
+ *
+ * @param string $param parameter name
+ * @param mixed $value parameter value
+ * @return $this
+ */
+ private function memorizeParam($param, $value)
+ {
+ if ($value && $this->catalogSession->getData($param) != $value) {
+ $this->catalogSession->setData($param, $value);
+ }
+ return $this;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ProductCategoryList.php b/app/code/Magento/Catalog/Model/ProductCategoryList.php
index 5bbae772d5c2b..c3a88a505c516 100644
--- a/app/code/Magento/Catalog/Model/ProductCategoryList.php
+++ b/app/code/Magento/Catalog/Model/ProductCategoryList.php
@@ -80,7 +80,10 @@ public function getCategoryIds($productId)
Select::SQL_UNION_ALL
);
- $this->categoryIdList[$productId] = $this->productResource->getConnection()->fetchCol($unionSelect);
+ $this->categoryIdList[$productId] = array_map(
+ 'intval',
+ $this->productResource->getConnection()->fetchCol($unionSelect)
+ );
}
return $this->categoryIdList[$productId];
diff --git a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
index 13f0dbf79a1b9..b96aff148e750 100644
--- a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
+++ b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
@@ -58,8 +58,8 @@ public function getCollection(\Magento\Catalog\Model\Product $product, $type)
}
usort($sorterItems, function ($itemA, $itemB) {
- $posA = intval($itemA['position']);
- $posB = intval($itemB['position']);
+ $posA = (int)$itemA['position'];
+ $posB = (int)$itemB['position'];
return $posA <=> $posB;
});
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
index 46bb74513b59c..4562aa0b0e75f 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/Product/CategoryLink.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php
index b54c19a111508..f11c975396f2e 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,16 @@ private function getCategoryLinkMetadata()
private function processCategoryLinks($newCategoryPositions, &$oldCategoryPositions)
{
$result = ['changed' => [], 'updated' => []];
+
+ $oldCategoryPositions = array_values($oldCategoryPositions);
+ $oldCategoryList = array_column($oldCategoryPositions, 'category_id');
foreach ($newCategoryPositions as $newCategoryPosition) {
- $key = array_search(
- $newCategoryPosition['category_id'],
- array_column($oldCategoryPositions, 'category_id')
- );
+ $key = array_search($newCategoryPosition['category_id'], $oldCategoryList);
if ($key === false) {
$result['changed'][] = $newCategoryPosition;
} elseif ($oldCategoryPositions[$key]['position'] != $newCategoryPosition['position']) {
- $result['updated'][] = $newCategoryPositions[$key];
+ $result['updated'][] = $newCategoryPosition;
unset($oldCategoryPositions[$key]);
}
}
@@ -132,6 +134,8 @@ private function processCategoryLinks($newCategoryPositions, &$oldCategoryPositi
}
/**
+ * Update category links
+ *
* @param ProductInterface $product
* @param array $insertLinks
* @param bool $insert
@@ -175,6 +179,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 0d62d120f80e0..14ae38667d873 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -1534,7 +1534,7 @@ public function addPriceData($customerGroupId = null, $websiteId = null)
/**
* Add attribute to filter
*
- * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute|string $attribute
+ * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute|string|array $attribute
* @param array $condition
* @param string $joinType
* @return $this
@@ -1809,7 +1809,8 @@ protected function _productLimitationJoinWebsite()
}
$conditions[] = $this->getConnection()->quoteInto(
'product_website.website_id IN(?)',
- $filters['website_ids']
+ $filters['website_ids'],
+ 'int'
);
} elseif (isset(
$filters['store_id']
@@ -1821,7 +1822,7 @@ protected function _productLimitationJoinWebsite()
) {
$joinWebsite = true;
$websiteId = $this->_storeManager->getStore($filters['store_id'])->getWebsiteId();
- $conditions[] = $this->getConnection()->quoteInto('product_website.website_id = ?', $websiteId);
+ $conditions[] = $this->getConnection()->quoteInto('product_website.website_id = ?', $websiteId, 'int');
}
$fromPart = $this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM);
@@ -2017,12 +2018,16 @@ protected function _applyProductLimitations()
$conditions = [
'cat_index.product_id=e.entity_id',
- $this->getConnection()->quoteInto('cat_index.store_id=?', $filters['store_id']),
+ $this->getConnection()->quoteInto('cat_index.store_id=?', $filters['store_id'], 'int'),
];
if (isset($filters['visibility']) && !isset($filters['store_table'])) {
- $conditions[] = $this->getConnection()->quoteInto('cat_index.visibility IN(?)', $filters['visibility']);
+ $conditions[] = $this->getConnection()->quoteInto(
+ 'cat_index.visibility IN(?)',
+ $filters['visibility'],
+ 'int'
+ );
}
- $conditions[] = $this->getConnection()->quoteInto('cat_index.category_id=?', $filters['category_id']);
+ $conditions[] = $this->getConnection()->quoteInto('cat_index.category_id=?', $filters['category_id'], 'int');
if (isset($filters['category_is_anchor'])) {
$conditions[] = $this->getConnection()->quoteInto('cat_index.is_parent=?', $filters['category_is_anchor']);
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php
index 2868392f85280..635715a60742f 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php
@@ -49,7 +49,8 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
protected function _construct()
@@ -58,7 +59,8 @@ protected function _construct()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
public function getConnection()
@@ -67,6 +69,8 @@ public function getConnection()
}
/**
+ * Load data from table by valueId
+ *
* @param string $tableNameAlias
* @param array $ids
* @param int|null $storeId
@@ -111,6 +115,8 @@ public function loadDataFromTableByValueId(
}
/**
+ * Load product gallery by attributeId
+ *
* @param \Magento\Catalog\Model\Product $product
* @param int $attributeId
* @return array
@@ -132,6 +138,8 @@ public function loadProductGalleryByAttributeId($product, $attributeId)
}
/**
+ * Create base load select
+ *
* @param int $entityId
* @param int $storeId
* @param int $attributeId
@@ -151,6 +159,8 @@ protected function createBaseLoadSelect($entityId, $storeId, $attributeId)
}
/**
+ * Create batch base select
+ *
* @param int $storeId
* @param int $attributeId
* @return \Magento\Framework\DB\Select
@@ -190,7 +200,7 @@ public function createBatchBaseSelect($storeId, $attributeId)
'value.' . $linkField . ' = entity.' . $linkField,
]
),
- ['label', 'position', 'disabled']
+ []
)->joinLeft(
['default_value' => $this->getTable(self::GALLERY_VALUE_TABLE)],
implode(
@@ -201,8 +211,15 @@ public function createBatchBaseSelect($storeId, $attributeId)
'default_value.' . $linkField . ' = entity.' . $linkField,
]
),
- ['label_default' => 'label', 'position_default' => 'position', 'disabled_default' => 'disabled']
- )->where(
+ []
+ )->columns([
+ 'label' => $this->getConnection()->getIfNullSql('`value`.`label`', '`default_value`.`label`'),
+ 'position' => $this->getConnection()->getIfNullSql('`value`.`position`', '`default_value`.`position`'),
+ 'disabled' => $this->getConnection()->getIfNullSql('`value`.`disabled`', '`default_value`.`disabled`'),
+ 'label_default' => 'default_value.label',
+ 'position_default' => 'default_value.position',
+ 'disabled_default' => 'default_value.disabled'
+ ])->where(
$mainTableAlias . '.attribute_id = ?',
$attributeId
)->where(
@@ -240,6 +257,8 @@ protected function removeDuplicates(&$result)
}
/**
+ * Get main table alias
+ *
* @return string
* @since 101.0.0
*/
@@ -249,6 +268,8 @@ public function getMainTableAlias()
}
/**
+ * Bind value to entity
+ *
* @param int $valueId
* @param int $entityId
* @return int
@@ -266,6 +287,8 @@ public function bindValueToEntity($valueId, $entityId)
}
/**
+ * Save data row
+ *
* @param string $table
* @param array $data
* @param array $fields
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php
index 123f358be40c8..77f67480619e0 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php
@@ -12,6 +12,9 @@
use Magento\Framework\DB\Select;
use Magento\Framework\App\ResourceConnection;
+/**
+ * Class for retrieval of all product images
+ */
class Image
{
/**
@@ -73,15 +76,24 @@ public function getAllProductImages(): \Generator
/**
* Get the number of unique pictures of products
+ *
* @return int
*/
public function getCountAllProductImages(): int
{
- $select = $this->getVisibleImagesSelect()->reset('columns')->columns('count(*)');
+ $select = $this->getVisibleImagesSelect()
+ ->reset('columns')
+ ->reset('distinct')
+ ->columns(
+ new \Zend_Db_Expr('count(distinct value)')
+ );
+
return (int) $this->connection->fetchOne($select);
}
/**
+ * Return Select to fetch all products images
+ *
* @return Select
*/
private function getVisibleImagesSelect(): Select
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 3a61e7e364454..7730d7cc9a7fd 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/Plugin/Framework/App/Action/ContextPlugin.php b/app/code/Magento/Catalog/Plugin/Framework/App/Action/ContextPlugin.php
new file mode 100644
index 0000000000000..6add542b15554
--- /dev/null
+++ b/app/code/Magento/Catalog/Plugin/Framework/App/Action/ContextPlugin.php
@@ -0,0 +1,73 @@
+toolbarMemorizer = $toolbarMemorizer;
+ $this->catalogSession = $catalogSession;
+ $this->httpContext = $httpContext;
+ }
+
+ /**
+ * Update http context with catalog sensitive information.
+ *
+ * @return void
+ */
+ public function beforeDispatch()
+ {
+ if ($this->toolbarMemorizer->isMemorizingAllowed()) {
+ $params = [
+ ToolbarModel::ORDER_PARAM_NAME,
+ ToolbarModel::DIRECTION_PARAM_NAME,
+ ToolbarModel::MODE_PARAM_NAME,
+ ToolbarModel::LIMIT_PARAM_NAME
+ ];
+ foreach ($params as $param) {
+ $paramValue = $this->catalogSession->getData($param);
+ if ($paramValue) {
+ $this->httpContext->setValue($param, $paramValue, false);
+ }
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php
index 54a13be864db7..77368517a3155 100644
--- a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php
+++ b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php
@@ -30,7 +30,7 @@ public function getValue()
$this->value = false;
foreach ($this->priceInfo->getPrices() as $price) {
if ($price instanceof BasePriceProviderInterface && $price->getValue() !== false) {
- $this->value = min($price->getValue(), $this->value ?: $price->getValue());
+ $this->value = min($price->getValue(), $this->value !== false ? $this->value: $price->getValue());
}
}
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
index 76f65381f43fa..57f91b78fcbe9 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
@@ -237,8 +237,30 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index 9f8d827b20849..01b31430793cc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -257,11 +257,10 @@
-
-
+
+
-
-
+
@@ -277,4 +276,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
index fd80838692065..80cadbb6571f2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
@@ -13,8 +13,38 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
index 1bd9bb4a09c86..fd1b6add8391f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
@@ -164,6 +164,13 @@
+
+
+
+
+
+
+
@@ -206,4 +213,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
index c980c43b8f3af..4c7c011028c92 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
@@ -15,12 +15,28 @@
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -50,4 +66,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeSetData.xml
new file mode 100644
index 0000000000000..d78c03a51dd75
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeSetData.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ test_set_
+ 7
+ 4
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml
new file mode 100644
index 0000000000000..d8ec84013d93b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+ GridPerPageValues
+ RememberCategoryPagination
+
+
+
+ 9,12,20,24
+
+
+
+ 1
+
+
+
+ DefaultCatalogStorefrontFlagZero
+ DefaultListAllowAll
+ DefaultFlatCatalogProduct
+
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 0
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
index a46d40c62c76e..a2bdaa7dbc62f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
@@ -16,4 +16,8 @@
0
attributeTwo
+
+ 0
+ attributeThree
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml
new file mode 100644
index 0000000000000..a2391dda54809
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+ adobe-base.jpg
+ adobe-base
+ jpg
+
+
+
+ adobe-small.jpg
+ adobe-small
+ jpg
+
+
+
+ adobe-thumb.jpg
+ adobe-thumb
+ jpg
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
index c575f1a5db82f..5be2a84f54555 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
@@ -65,4 +65,20 @@
false
0
+
+
+ Green
+ false
+ 3
+ Option7Store0
+ Option8Store1
+
+
+
+ Red
+ false
+ 3
+ Option9Store0
+ Option10Store1
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 9ae551b69d388..e5b23fe3a3c34 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -18,6 +18,7 @@
testurlkey
1
100
+ 1
EavStockItem
CustomAttributeCategoryIds
@@ -301,6 +302,20 @@
EavStockItem
CustomAttributeCategoryIds
+
+ testSku
+ simple
+
+ 4
+ testProductName
+ 123.00
+ testurlkey
+ 1
+ 1
+ 100
+ EavStockItem
+ CustomAttributeCategoryIds
+
magento.jpg
@@ -419,4 +434,47 @@
1
EavStock100
+
+ testSku
+ simple
+ 4
+ 4
+ massUpdateProductName
+ massUpdateProductName
+ 123.00
+ masstesturlkey
+ 1
+ 100
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ testSku
+ simple
+ 4
+ 4
+ massUpdateProductName
+ massUpdateProductName
+ 123.00
+ masstesturlkey
+ 1
+ 100
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ api-simple-product
+ simple
+ 4
+ 4
+ Api Simple Product
+ 123.00
+ api-simple-product
+ 1
+ 1
+ EavStock1
+ CustomAttributeProductAttribute
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
index 6e532637fb6d3..e9e9e43752365 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
@@ -17,4 +17,7 @@
Qty_10
+
+ Qty_1
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
index 39ecc2d440fc2..7cba4c3c76fe9 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
@@ -28,4 +28,8 @@
101
true
+
+ 1
+ true
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml
index ce964e2d71503..0e51995ac72e8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml
@@ -56,4 +56,20 @@
1
option6
+
+ 0
+ Green
+
+
+ 1
+ Green
+
+
+ 0
+ Red
+
+
+ 1
+ Red
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_attribute_set-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_attribute_set-meta.xml
new file mode 100644
index 0000000000000..9ef7b507812a0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_attribute_set-meta.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ application/json
+
+ string
+ integer
+
+ integer
+
+
+ application/json
+
+
+ application/json
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_configuration-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_configuration-meta.xml
new file mode 100644
index 0000000000000..b1f2b43220b36
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_configuration-meta.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+ string
+
+
+ string
+
+
+ string
+
+
+ string
+
+
+ string
+
+
+ string
+
+
+ integer
+
+
+ integer
+
+
+ integer
+
+
+ integer
+
+
+ string
+
+
+ integer
+
+
+
+
+
+
+
+
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+
+ integer
+
+
+
+ integer
+
+
+ integer
+
+
+
+
+
+
diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/Metadata/image_content-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/image_content-meta.xml
similarity index 100%
rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Framework/Metadata/image_content-meta.xml
rename to app/code/Magento/Catalog/Test/Mftf/Metadata/image_content-meta.xml
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
index fc776b49ba213..b3ed3f478f810 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
@@ -17,5 +17,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
index 5aaa78822af08..75e3210cad7d4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
index 8b69a44993f17..9a0dd8f5b387d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
@@ -32,6 +32,13 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
index ed0325394d591..acee714fe3ec7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
@@ -15,5 +15,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryDisplaySettingsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryDisplaySettingsSection.xml
new file mode 100644
index 0000000000000..daa00eb0a27b7
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryDisplaySettingsSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
index 6b754dcc5d482..40ea919226d6e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
@@ -14,5 +14,6 @@
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
index b83676c2e1033..324f261f3a50a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
@@ -17,12 +17,17 @@
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml
new file mode 100644
index 0000000000000..0f438540603d0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml
index ed08c84cdb6f8..06ff54b2a3997 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml
@@ -15,7 +15,7 @@
-
+
@@ -28,5 +28,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
index afbaba41a9bb7..c7d4cd16d788a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
@@ -15,5 +15,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
new file mode 100644
index 0000000000000..0314534dcddfb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
index 51199300206a9..249610568aec7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
@@ -28,6 +28,7 @@
+
@@ -106,24 +107,24 @@
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
+
@@ -144,21 +145,21 @@
-
+
-
+
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
index a7e20e22f1ddc..59228d57502f1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
@@ -28,6 +28,8 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagePlaceholderConfigSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagePlaceholderConfigSection.xml
new file mode 100644
index 0000000000000..7558b13d624bb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagePlaceholderConfigSection.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
index d484abf2069ff..03566be55ad2f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
@@ -9,6 +9,9 @@
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
index 7ea74d7913758..178e58ef2d649 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/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml
new file mode 100644
index 0000000000000..ff2e5f2f36015
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
index bd1c057133d83..03f3e93bb30ec 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
@@ -16,82 +16,77 @@
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
-
-
-
-
+
-
-
+
+
+
-
-
+
-
+
-
-
+
+
+
-
+
-
-
+
-
-
-
-
+
+
-
+
+
+
-
-
-
-
+
+
+
-
+
@@ -107,9 +102,5 @@
-
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml
new file mode 100644
index 0000000000000..4d97dee56f059
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $getSmallPlaceholderImageSrc
+ {{placeholderSmallImage.name}}
+
+
+
+
+
+ $getSmallNonPlaceholderImageSrc
+ {{placeholderSmallImage.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $getThumbnailPlaceholderImageSrc
+ {{placeholderThumbnailImage.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $getThumbnailImageSrc
+ {{placeholderThumbnailImage.name}}
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml
index bfb9557910642..8806612c0f5de 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml
@@ -69,4 +69,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml
new file mode 100644
index 0000000000000..525f81de6c48c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml
index 95d74b9653113..6658ad36d7150 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml
@@ -40,4 +40,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml
index 7603400ba8dcd..e914b8c96d03e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml
@@ -26,10 +26,9 @@
-
-
-
-
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml
new file mode 100644
index 0000000000000..9e323745835c2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest.xml
new file mode 100644
index 0000000000000..e9b54e3f1a3dc
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductStatusStoreViewScopeTest.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml
index 69931395a35d5..1cd0e15780c11 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest.xml
@@ -87,10 +87,9 @@
-
-
+
@@ -115,8 +114,7 @@
-
-
+
@@ -136,9 +134,9 @@
-
-
-
+
+
+
@@ -154,16 +152,16 @@
-
+
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml
index 6b444f1f6663b..c845a27773170 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml
@@ -103,12 +103,15 @@
-
+
+
+
-
+
+
+
-
-
+
@@ -119,6 +122,7 @@
+
@@ -141,6 +145,7 @@
+
@@ -179,4 +184,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
index a03636e52ee97..fcdb92d71f1f8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
@@ -71,11 +71,17 @@
+
+
+
+
+
-
+
+
@@ -86,6 +92,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRememberCategoryPaginationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRememberCategoryPaginationTest.xml
new file mode 100644
index 0000000000000..0ed61b8636c4f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRememberCategoryPaginationTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml
new file mode 100644
index 0000000000000..f283a040ced41
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php
index 8a42865a3fe4d..95b06e40602bf 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php
@@ -145,7 +145,8 @@ private function getTestDataWithoutAttributes(): array
'label' => 'test_image_label',
'ratio' => 1,
'custom_attributes' => '',
- 'product_id' => null
+ 'product_id' => null,
+ 'class' => 'product-image-photo'
],
],
];
@@ -190,6 +191,7 @@ private function getTestDataWithAttributes(): array
'custom_attributes' => [
'name_1' => 'value_1',
'name_2' => 'value_2',
+ 'class' => 'my-class'
],
],
'expected' => [
@@ -201,7 +203,8 @@ private function getTestDataWithAttributes(): array
'label' => 'test_product_name',
'ratio' => 0.5, // <==
'custom_attributes' => 'name_1="value_1" name_2="value_2"',
- 'product_id' => null
+ 'product_id' => null,
+ 'class' => 'my-class'
],
],
];
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ProductList/ToolbarTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ProductList/ToolbarTest.php
index ac963326dbfa1..884f4c543c8b8 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ProductList/ToolbarTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ProductList/ToolbarTest.php
@@ -18,6 +18,11 @@ class ToolbarTest extends \PHPUnit\Framework\TestCase
*/
protected $model;
+ /**
+ * @var \Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer | \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $memorizer;
+
/**
* @var \Magento\Framework\Url | \PHPUnit_Framework_MockObject_MockObject
*/
@@ -62,6 +67,16 @@ protected function setUp()
'getLimit',
'getCurrentPage'
]);
+ $this->memorizer = $this->createPartialMock(
+ \Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer::class,
+ [
+ 'getDirection',
+ 'getOrder',
+ 'getMode',
+ 'getLimit',
+ 'isMemorizingAllowed'
+ ]
+ );
$this->layout = $this->createPartialMock(\Magento\Framework\View\Layout::class, ['getChildName', 'getBlock']);
$this->pagerBlock = $this->createPartialMock(\Magento\Theme\Block\Html\Pager::class, [
'setUseContainer',
@@ -116,6 +131,7 @@ protected function setUp()
'context' => $context,
'catalogConfig' => $this->catalogConfig,
'toolbarModel' => $this->model,
+ 'toolbarMemorizer' => $this->memorizer,
'urlEncoder' => $this->urlEncoder,
'productListHelper' => $this->productListHelper
]
@@ -155,7 +171,7 @@ public function testGetPagerEncodedUrl()
public function testGetCurrentOrder()
{
$order = 'price';
- $this->model->expects($this->once())
+ $this->memorizer->expects($this->once())
->method('getOrder')
->will($this->returnValue($order));
$this->catalogConfig->expects($this->once())
@@ -169,7 +185,7 @@ public function testGetCurrentDirection()
{
$direction = 'desc';
- $this->model->expects($this->once())
+ $this->memorizer->expects($this->once())
->method('getDirection')
->will($this->returnValue($direction));
@@ -183,7 +199,7 @@ public function testGetCurrentMode()
$this->productListHelper->expects($this->once())
->method('getAvailableViewMode')
->will($this->returnValue(['list' => 'List']));
- $this->model->expects($this->once())
+ $this->memorizer->expects($this->once())
->method('getMode')
->will($this->returnValue($mode));
@@ -232,11 +248,11 @@ public function testGetLimit()
$mode = 'list';
$limit = 10;
- $this->model->expects($this->once())
+ $this->memorizer->expects($this->once())
->method('getMode')
->will($this->returnValue($mode));
- $this->model->expects($this->once())
+ $this->memorizer->expects($this->once())
->method('getLimit')
->will($this->returnValue($limit));
$this->productListHelper->expects($this->once())
@@ -266,7 +282,7 @@ public function testGetPagerHtml()
$this->productListHelper->expects($this->exactly(2))
->method('getAvailableLimit')
->will($this->returnValue([10 => 10, 20 => 20]));
- $this->model->expects($this->once())
+ $this->memorizer->expects($this->once())
->method('getLimit')
->will($this->returnValue($limit));
$this->pagerBlock->expects($this->once())
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php
index f493cbc88f18e..ced65b2d2e15d 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php
@@ -5,7 +5,9 @@
*/
namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Attribute;
+use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save;
+use Magento\Framework\Serialize\Serializer\FormData;
use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest;
use Magento\Catalog\Model\Product\AttributeSet\BuildFactory;
use Magento\Catalog\Model\Product\AttributeSet\Build;
@@ -13,11 +15,14 @@
use Magento\Eav\Api\Data\AttributeSetInterface;
use Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory;
+use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Filter\FilterManager;
use Magento\Catalog\Helper\Product as ProductHelper;
+use Magento\Framework\View\Element\Messages;
use Magento\Framework\View\LayoutFactory;
use Magento\Backend\Model\View\Result\Redirect as ResultRedirect;
use Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\Validator as InputTypeValidator;
+use Magento\Framework\View\LayoutInterface;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -79,6 +84,16 @@ class SaveTest extends AttributeTest
*/
protected $inputTypeValidatorMock;
+ /**
+ * @var FormData|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $formDataSerializerMock;
+
+ /**
+ * @var ProductAttributeInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $productAttributeMock;
+
protected function setUp()
{
parent::setUp();
@@ -108,6 +123,7 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
$this->redirectMock = $this->getMockBuilder(ResultRedirect::class)
+ ->setMethods(['setData', 'setPath'])
->disableOriginalConstructor()
->getMock();
$this->attributeSetMock = $this->getMockBuilder(AttributeSetInterface::class)
@@ -119,6 +135,12 @@ protected function setUp()
$this->inputTypeValidatorMock = $this->getMockBuilder(InputTypeValidator::class)
->disableOriginalConstructor()
->getMock();
+ $this->formDataSerializerMock = $this->getMockBuilder(FormData::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class)
+ ->setMethods(['getId', 'get'])
+ ->getMockForAbstractClass();
$this->buildFactoryMock->expects($this->any())
->method('create')
@@ -126,6 +148,9 @@ protected function setUp()
$this->validatorFactoryMock->expects($this->any())
->method('create')
->willReturn($this->inputTypeValidatorMock);
+ $this->attributeFactoryMock
+ ->method('create')
+ ->willReturn($this->productAttributeMock);
}
/**
@@ -145,11 +170,23 @@ protected function getModel()
'validatorFactory' => $this->validatorFactoryMock,
'groupCollectionFactory' => $this->groupCollectionFactoryMock,
'layoutFactory' => $this->layoutFactoryMock,
+ 'formDataSerializer' => $this->formDataSerializerMock,
]);
}
public function testExecuteWithEmptyData()
{
+ $this->requestMock->expects($this->any())
+ ->method('getParam')
+ ->willReturnMap([
+ ['isAjax', null, null],
+ ['serialized_options', '[]', ''],
+ ]);
+ $this->formDataSerializerMock
+ ->expects($this->once())
+ ->method('unserialize')
+ ->with('')
+ ->willReturn([]);
$this->requestMock->expects($this->once())
->method('getPostValue')
->willReturn([]);
@@ -170,6 +207,23 @@ public function testExecute()
'frontend_input' => 'test_frontend_input',
];
+ $this->requestMock->expects($this->any())
+ ->method('getParam')
+ ->willReturnMap([
+ ['isAjax', null, null],
+ ['serialized_options', '[]', ''],
+ ]);
+ $this->formDataSerializerMock
+ ->expects($this->once())
+ ->method('unserialize')
+ ->with('')
+ ->willReturn([]);
+ $this->productAttributeMock
+ ->method('getId')
+ ->willReturn(1);
+ $this->productAttributeMock
+ ->method('getAttributeCode')
+ ->willReturn('test_code');
$this->requestMock->expects($this->once())
->method('getPostValue')
->willReturn($data);
@@ -203,4 +257,80 @@ public function testExecute()
$this->assertInstanceOf(ResultRedirect::class, $this->getModel()->execute());
}
+
+ /**
+ * @throws \Magento\Framework\Exception\NotFoundException
+ */
+ public function testExecuteWithOptionsDataError()
+ {
+ $serializedOptions = '{"key":"value"}';
+ $message = "The attribute couldn't be saved due to an error. Verify your information and try again. "
+ . "If the error persists, please try again later.";
+
+ $this->requestMock->expects($this->any())
+ ->method('getParam')
+ ->willReturnMap([
+ ['isAjax', null, true],
+ ['serialized_options', '[]', $serializedOptions],
+ ]);
+ $this->formDataSerializerMock
+ ->expects($this->once())
+ ->method('unserialize')
+ ->with($serializedOptions)
+ ->willThrowException(new \InvalidArgumentException('Some exception'));
+ $this->messageManager
+ ->expects($this->once())
+ ->method('addErrorMessage')
+ ->with($message);
+ $this->addReturnResultConditions('catalog/*/edit', ['_current' => true], ['error' => true]);
+
+ $this->getModel()->execute();
+ }
+
+ /**
+ * @param string $path
+ * @param array $params
+ * @param array $response
+ * @return mixed
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ private function addReturnResultConditions(string $path = '', array $params = [], array $response = [])
+ {
+ $layoutMock = $this->getMockBuilder(LayoutInterface::class)
+ ->setMethods(['initMessages', 'getMessagesBlock'])
+ ->getMockForAbstractClass();
+ $this->layoutFactoryMock
+ ->expects($this->once())
+ ->method('create')
+ ->with()
+ ->willReturn($layoutMock);
+ $layoutMock
+ ->method('initMessages')
+ ->with();
+ $messageBlockMock = $this->getMockBuilder(Messages::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $layoutMock
+ ->expects($this->once())
+ ->method('getMessagesBlock')
+ ->willReturn($messageBlockMock);
+ $messageBlockMock
+ ->expects($this->once())
+ ->method('getGroupedHtml')
+ ->willReturn('message1');
+ $this->resultFactoryMock
+ ->expects($this->once())
+ ->method('create')
+ ->with(ResultFactory::TYPE_JSON)
+ ->willReturn($this->redirectMock);
+ $response = array_merge($response, [
+ 'messages' => ['message1'],
+ 'params' => $params,
+ ]);
+ $this->redirectMock
+ ->expects($this->once())
+ ->method('setData')
+ ->with($response)
+ ->willReturnSelf();
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php
index 9c747393cc72a..c6210f93e1290 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Attribute;
use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate;
+use Magento\Framework\Serialize\Serializer\FormData;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest;
use Magento\Eav\Model\Entity\Attribute\Set as AttributeSet;
@@ -61,6 +62,11 @@ class ValidateTest extends AttributeTest
*/
protected $layoutMock;
+ /**
+ * @var FormData|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $formDataSerializerMock;
+
protected function setUp()
{
parent::setUp();
@@ -86,6 +92,9 @@ protected function setUp()
->getMock();
$this->layoutMock = $this->getMockBuilder(LayoutInterface::class)
->getMockForAbstractClass();
+ $this->formDataSerializerMock = $this->getMockBuilder(FormData::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->contextMock->expects($this->any())
->method('getObjectManager')
@@ -100,25 +109,28 @@ protected function getModel()
return $this->objectManager->getObject(
Validate::class,
[
- 'context' => $this->contextMock,
- 'attributeLabelCache' => $this->attributeLabelCacheMock,
- 'coreRegistry' => $this->coreRegistryMock,
- 'resultPageFactory' => $this->resultPageFactoryMock,
- 'resultJsonFactory' => $this->resultJsonFactoryMock,
- 'layoutFactory' => $this->layoutFactoryMock,
- 'multipleAttributeList' => ['select' => 'option']
+ 'context' => $this->contextMock,
+ 'attributeLabelCache' => $this->attributeLabelCacheMock,
+ 'coreRegistry' => $this->coreRegistryMock,
+ 'resultPageFactory' => $this->resultPageFactoryMock,
+ 'resultJsonFactory' => $this->resultJsonFactoryMock,
+ 'layoutFactory' => $this->layoutFactoryMock,
+ 'multipleAttributeList' => ['select' => 'option'],
+ 'formDataSerializer' => $this->formDataSerializerMock,
]
);
}
public function testExecute()
{
+ $serializedOptions = '{"key":"value"}';
$this->requestMock->expects($this->any())
->method('getParam')
->willReturnMap([
['frontend_label', null, 'test_frontend_label'],
['attribute_code', null, 'test_attribute_code'],
['new_attribute_set_name', null, 'test_attribute_set_name'],
+ ['serialized_options', '[]', $serializedOptions],
]);
$this->objectManagerMock->expects($this->exactly(2))
->method('create')
@@ -160,6 +172,7 @@ public function testExecute()
*/
public function testUniqueValidation(array $options, $isError)
{
+ $serializedOptions = '{"key":"value"}';
$countFunctionCalls = ($isError) ? 6 : 5;
$this->requestMock->expects($this->exactly($countFunctionCalls))
->method('getParam')
@@ -167,10 +180,16 @@ public function testUniqueValidation(array $options, $isError)
['frontend_label', null, null],
['attribute_code', null, "test_attribute_code"],
['new_attribute_set_name', null, 'test_attribute_set_name'],
- ['option', null, $options],
- ['message_key', null, Validate::DEFAULT_MESSAGE_KEY]
+ ['message_key', null, Validate::DEFAULT_MESSAGE_KEY],
+ ['serialized_options', '[]', $serializedOptions],
]);
+ $this->formDataSerializerMock
+ ->expects($this->once())
+ ->method('unserialize')
+ ->with($serializedOptions)
+ ->willReturn($options);
+
$this->objectManagerMock->expects($this->once())
->method('create')
->willReturn($this->attributeMock);
@@ -203,67 +222,77 @@ public function provideUniqueData()
return [
'no values' => [
[
- 'delete' => [
- "option_0" => "",
- "option_1" => "",
- "option_2" => "",
- ]
+ 'option' => [
+ 'delete' => [
+ "option_0" => "",
+ "option_1" => "",
+ "option_2" => "",
+ ],
+ ],
], false
],
'valid options' => [
[
- 'value' => [
- "option_0" => [1, 0],
- "option_1" => [2, 0],
- "option_2" => [3, 0],
+ 'option' => [
+ 'value' => [
+ "option_0" => [1, 0],
+ "option_1" => [2, 0],
+ "option_2" => [3, 0],
+ ],
+ 'delete' => [
+ "option_0" => "",
+ "option_1" => "",
+ "option_2" => "",
+ ],
],
- 'delete' => [
- "option_0" => "",
- "option_1" => "",
- "option_2" => "",
- ]
], false
],
'duplicate options' => [
[
- 'value' => [
- "option_0" => [1, 0],
- "option_1" => [1, 0],
- "option_2" => [3, 0],
+ 'option' => [
+ 'value' => [
+ "option_0" => [1, 0],
+ "option_1" => [1, 0],
+ "option_2" => [3, 0],
+ ],
+ 'delete' => [
+ "option_0" => "",
+ "option_1" => "",
+ "option_2" => "",
+ ],
],
- 'delete' => [
- "option_0" => "",
- "option_1" => "",
- "option_2" => "",
- ]
], true
],
'duplicate and deleted' => [
[
- 'value' => [
- "option_0" => [1, 0],
- "option_1" => [1, 0],
- "option_2" => [3, 0],
+ 'option' => [
+ 'value' => [
+ "option_0" => [1, 0],
+ "option_1" => [1, 0],
+ "option_2" => [3, 0],
+ ],
+ 'delete' => [
+ "option_0" => "",
+ "option_1" => "1",
+ "option_2" => "",
+ ],
],
- 'delete' => [
- "option_0" => "",
- "option_1" => "1",
- "option_2" => "",
- ]
], false
],
'empty and deleted' => [
[
- 'value' => [
- "option_0" => [1, 0],
- "option_1" => [2, 0],
- "option_2" => ["", ""],
+ 'option' => [
+ 'value' => [
+ "option_0" => [1, 0],
+ "option_1" => [2, 0],
+ "option_2" => ["", ""],
+ ],
+ 'delete' => [
+ "option_0" => "",
+ "option_1" => "",
+ "option_2" => "1",
+ ],
],
- 'delete' => [
- "option_0" => "",
- "option_1" => "",
- "option_2" => "1",
- ]
], false
],
];
@@ -278,6 +307,7 @@ public function provideUniqueData()
*/
public function testEmptyOption(array $options, $result)
{
+ $serializedOptions = '{"key":"value"}';
$this->requestMock->expects($this->any())
->method('getParam')
->willReturnMap([
@@ -285,10 +315,16 @@ public function testEmptyOption(array $options, $result)
['frontend_input', 'select', 'multipleselect'],
['attribute_code', null, "test_attribute_code"],
['new_attribute_set_name', null, 'test_attribute_set_name'],
- ['option', null, $options],
['message_key', Validate::DEFAULT_MESSAGE_KEY, 'message'],
+ ['serialized_options', '[]', $serializedOptions],
]);
+ $this->formDataSerializerMock
+ ->expects($this->once())
+ ->method('unserialize')
+ ->with($serializedOptions)
+ ->willReturn($options);
+
$this->objectManagerMock->expects($this->once())
->method('create')
->willReturn($this->attributeMock);
@@ -320,8 +356,10 @@ public function provideEmptyOption()
return [
'empty admin scope options' => [
[
- 'value' => [
- "option_0" => [''],
+ 'option' => [
+ 'value' => [
+ "option_0" => [''],
+ ],
],
],
(object) [
@@ -331,8 +369,10 @@ public function provideEmptyOption()
],
'not empty admin scope options' => [
[
- 'value' => [
- "option_0" => ['asdads'],
+ 'option' => [
+ 'value' => [
+ "option_0" => ['asdads'],
+ ],
],
],
(object) [
@@ -341,11 +381,13 @@ public function provideEmptyOption()
],
'empty admin scope options and deleted' => [
[
- 'value' => [
- "option_0" => [''],
- ],
- 'delete' => [
- 'option_0' => '1',
+ 'option' => [
+ 'value' => [
+ "option_0" => [''],
+ ],
+ 'delete' => [
+ 'option_0' => '1',
+ ],
],
],
(object) [
@@ -354,11 +396,13 @@ public function provideEmptyOption()
],
'empty admin scope options and not deleted' => [
[
- 'value' => [
- "option_0" => [''],
- ],
- 'delete' => [
- 'option_0' => '0',
+ 'option' => [
+ 'value' => [
+ "option_0" => [''],
+ ],
+ 'delete' => [
+ 'option_0' => '0',
+ ],
],
],
(object) [
@@ -368,4 +412,55 @@ public function provideEmptyOption()
],
];
}
+
+ /**
+ * @throws \Magento\Framework\Exception\NotFoundException
+ */
+ public function testExecuteWithOptionsDataError()
+ {
+ $serializedOptions = '{"key":"value"}';
+ $message = "The attribute couldn't be validated due to an error. Verify your information and try again. "
+ . "If the error persists, please try again later.";
+ $this->requestMock->expects($this->any())
+ ->method('getParam')
+ ->willReturnMap([
+ ['frontend_label', null, 'test_frontend_label'],
+ ['attribute_code', null, 'test_attribute_code'],
+ ['new_attribute_set_name', null, 'test_attribute_set_name'],
+ ['message_key', Validate::DEFAULT_MESSAGE_KEY, 'message'],
+ ['serialized_options', '[]', $serializedOptions],
+ ]);
+
+ $this->formDataSerializerMock
+ ->expects($this->once())
+ ->method('unserialize')
+ ->with($serializedOptions)
+ ->willThrowException(new \InvalidArgumentException('Some exception'));
+
+ $this->objectManagerMock
+ ->method('create')
+ ->willReturnMap([
+ [\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class, [], $this->attributeMock],
+ [\Magento\Eav\Model\Entity\Attribute\Set::class, [], $this->attributeSetMock]
+ ]);
+
+ $this->attributeMock
+ ->method('loadByCode')
+ ->willReturnSelf();
+ $this->attributeSetMock
+ ->method('setEntityTypeId')
+ ->willReturnSelf();
+ $this->resultJsonFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->resultJson);
+ $this->resultJson->expects($this->once())
+ ->method('setJsonData')
+ ->with(json_encode([
+ 'error' => true,
+ 'message' => $message
+ ]))
+ ->willReturnSelf();
+
+ $this->getModel()->execute();
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php
index b85b03852b621..2a75773754fca 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AttributeTest.php
@@ -9,8 +9,10 @@
use Magento\Catalog\Controller\Adminhtml\Product\Attribute;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Cache\FrontendInterface;
+use Magento\Framework\Message\ManagerInterface;
+use Magento\Framework\ObjectManager\ObjectManager;
use Magento\Framework\Registry;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use Magento\Framework\View\Result\PageFactory;
use Magento\Framework\Controller\ResultFactory;
@@ -20,7 +22,7 @@
class AttributeTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var ObjectManager
+ * @var ObjectManagerHelper
*/
protected $objectManager;
@@ -54,9 +56,14 @@ class AttributeTest extends \PHPUnit\Framework\TestCase
*/
protected $resultFactoryMock;
+ /**
+ * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $messageManager;
+
protected function setUp()
{
- $this->objectManager = new ObjectManager($this);
+ $this->objectManager = new ObjectManagerHelper($this);
$this->contextMock = $this->getMockBuilder(Context::class)
->disableOriginalConstructor()
->getMock();
@@ -74,6 +81,9 @@ protected function setUp()
$this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class)
->disableOriginalConstructor()
->getMock();
+ $this->messageManager = $this->getMockBuilder(ManagerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->contextMock->expects($this->any())
->method('getRequest')
@@ -81,6 +91,9 @@ protected function setUp()
$this->contextMock->expects($this->any())
->method('getResultFactory')
->willReturn($this->resultFactoryMock);
+ $this->contextMock
+ ->method('getMessageManager')
+ ->willReturn($this->messageManager);
}
/**
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php
index ff44a91a64998..c889c58e3df3a 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php
@@ -95,6 +95,11 @@ class HelperTest extends \PHPUnit\Framework\TestCase
*/
protected $attributeFilterMock;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $dateTimeFilterMock;
+
/**
* @inheritdoc
*/
@@ -170,6 +175,11 @@ protected function setUp()
$resolverProperty = $helperReflection->getProperty('linkResolver');
$resolverProperty->setAccessible(true);
$resolverProperty->setValue($this->helper, $this->linkResolverMock);
+
+ $this->dateTimeFilterMock = $this->createMock(\Magento\Framework\Stdlib\DateTime\Filter\DateTime::class);
+ $dateTimeFilterProperty = $helperReflection->getProperty('dateTimeFilter');
+ $dateTimeFilterProperty->setAccessible(true);
+ $dateTimeFilterProperty->setValue($this->helper, $this->dateTimeFilterMock);
}
/**
@@ -211,6 +221,12 @@ public function testInitialize(
if (!empty($tierPrice)) {
$productData = array_merge($productData, ['tier_price' => $tierPrice]);
}
+
+ $this->dateTimeFilterMock->expects($this->once())
+ ->method('filter')
+ ->with($specialFromDate)
+ ->willReturn($specialFromDate);
+
$attributeNonDate = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)
->disableOriginalConstructor()
->getMock();
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php
index 3572cb9d87f0c..cce00c50d37af 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php
@@ -120,8 +120,13 @@ public function testExecute(): void
['entity_id', $productId]
]
);
- $product->expects($this->atLeastOnce())->method('getOrigData')->with('tier_price')
- ->willReturn($originalTierPrices);
+ $product->expects($this->atLeastOnce())->method('getOrigData')
+ ->willReturnMap(
+ [
+ ['tier_price', $originalTierPrices],
+ ['entity_id', $productId]
+ ]
+ );
$product->expects($this->atLeastOnce())->method('getStoreId')->willReturn(0);
$product->expects($this->atLeastOnce())->method('setData')->with('tier_price_changed', 1);
$store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)
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 1ff3a1bae5c28..7ad8b1a0ab3f8 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/ImageUploaderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php
index c989f2dd47462..6552e85440008 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php
@@ -69,10 +69,17 @@ class ImageUploaderTest extends \PHPUnit\Framework\TestCase
/**
* Allowed extensions
*
- * @var string
+ * @var array
*/
private $allowedExtensions;
+ /**
+ * Allowed mime types
+ *
+ * @var array
+ */
+ private $allowedMimeTypes;
+
protected function setUp()
{
$this->coreFileStorageDatabaseMock = $this->createMock(
@@ -97,6 +104,7 @@ protected function setUp()
$this->baseTmpPath = 'base/tmp/';
$this->basePath = 'base/real/';
$this->allowedExtensions = ['.jpg'];
+ $this->allowedMimeTypes = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png'];
$this->imageUploader =
new \Magento\Catalog\Model\ImageUploader(
@@ -107,7 +115,8 @@ protected function setUp()
$this->loggerMock,
$this->baseTmpPath,
$this->basePath,
- $this->allowedExtensions
+ $this->allowedExtensions,
+ $this->allowedMimeTypes
);
}
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 c04428eadef0d..e1e2816d44220 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 85d8dd8a14917..11d07872fef91 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/CopierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
index 31fd0696db320..e9eee5c766883 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Api\Data\ProductInterface;
use \Magento\Catalog\Model\Product\Copier;
+use Magento\Catalog\Model\Product;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -54,7 +55,7 @@ protected function setUp()
\Magento\Catalog\Model\Product\Option\Repository::class
);
$this->optionRepositoryMock;
- $this->productMock = $this->createMock(\Magento\Catalog\Model\Product::class);
+ $this->productMock = $this->createMock(Product::class);
$this->productMock->expects($this->any())->method('getEntityId')->willReturn(1);
$this->metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadata::class)
@@ -106,7 +107,7 @@ public function testCopy()
$this->productMock->expects($this->once())->method('getResource')->will($this->returnValue($resourceMock));
$duplicateMock = $this->createPartialMock(
- \Magento\Catalog\Model\Product::class,
+ Product::class,
[
'__wakeup',
'setData',
@@ -147,10 +148,10 @@ public function testCopy()
)->with(
\Magento\Store\Model\Store::DEFAULT_STORE_ID
);
- $duplicateMock->expects($this->once())->method('setData')->with($productData);
+ $duplicateMock->expects($this->atLeastOnce())->method('setData')->willReturn($duplicateMock);
$this->copyConstructorMock->expects($this->once())->method('build')->with($this->productMock, $duplicateMock);
$duplicateMock->expects($this->once())->method('getUrlKey')->willReturn('urk-key-1');
- $duplicateMock->expects($this->once())->method('setUrlKey')->with('urk-key-2');
+ $duplicateMock->expects($this->once())->method('setUrlKey')->with('urk-key-2')->willReturn($duplicateMock);
$duplicateMock->expects($this->once())->method('save');
$this->metadata->expects($this->any())->method('getLinkField')->willReturn('linkField');
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
index 9fafbc9d9675b..1d12645019d1e 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
@@ -266,7 +266,7 @@ public function testGetWithNonExistingProduct()
/**
* @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedExceptionText The image doesn't exist. Verify and try again.
+ * @expectedExceptionMessage The image doesn't exist. Verify and try again.
*/
public function testGetWithNonExistingImage()
{
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php
index 754d80302d410..6029a2b820086 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php
@@ -54,7 +54,7 @@ protected function setUp()
/**
* @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedMessage Tier price is unavailable for this product.
+ * @expectedExceptionMessage Product hasn't group price with such data: customerGroupId = '1', website = 1, qty = 3
*/
public function testRemoveWhenTierPricesNotExists()
{
@@ -70,7 +70,7 @@ public function testRemoveWhenTierPricesNotExists()
/**
* @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedMessage For current customerGroupId = '10' with 'qty' = 15 any tier price exist'.
+ * @expectedExceptionMessage Product hasn't group price with such data: customerGroupId = '10', website = 1, qty = 5
*/
public function testRemoveTierPriceForNonExistingCustomerGroup()
{
@@ -81,7 +81,7 @@ public function testRemoveTierPriceForNonExistingCustomerGroup()
->will($this->returnValue($this->prices));
$this->productMock->expects($this->never())->method('setData');
$this->productRepositoryMock->expects($this->never())->method('save');
- $this->priceModifier->removeTierPrice($this->productMock, 10, 15, 1);
+ $this->priceModifier->removeTierPrice($this->productMock, 10, 5, 1);
}
public function testSuccessfullyRemoveTierPriceSpecifiedForAllGroups()
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php
index f340d0b204b62..ae479a9b34d48 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php
@@ -195,7 +195,7 @@ public function testSuccessDeleteTierPrice()
/**
* @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @message The product doesn't exist. Verify and try again.
+ * @expectedExceptionMessage No such entity.
*/
public function testDeleteTierPriceFromNonExistingProduct()
{
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php
index dfed4e4f37385..47ef3c999125f 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php
@@ -281,6 +281,9 @@ public function testBindValueToEntityRecordExists()
$this->resource->bindValueToEntity($valueId, $entityId);
}
+ /**
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
public function testLoadGallery()
{
$productId = 5;
@@ -329,7 +332,8 @@ public function testLoadGallery()
'main.value_id = entity.value_id',
['entity_id']
)->willReturnSelf();
- $this->product->expects($this->at(0))->method('getData')->with('entity_id')->willReturn($productId);
+ $this->product->expects($this->at(0))->method('getData')
+ ->with('entity_id')->willReturn($productId);
$this->product->expects($this->at(1))->method('getStoreId')->will($this->returnValue($storeId));
$this->connection->expects($this->exactly(2))->method('quoteInto')->withConsecutive(
['value.store_id = ?'],
@@ -338,26 +342,50 @@ public function testLoadGallery()
'value.store_id = ' . $storeId,
'default_value.store_id = ' . 0
);
+ $this->connection->expects($this->any())->method('getIfNullSql')->will(
+ $this->returnValueMap([
+ [
+ '`value`.`label`',
+ '`default_value`.`label`',
+ 'IFNULL(`value`.`label`, `default_value`.`label`)'
+ ],
+ [
+ '`value`.`position`',
+ '`default_value`.`position`',
+ 'IFNULL(`value`.`position`, `default_value`.`position`)'
+ ],
+ [
+ '`value`.`disabled`',
+ '`default_value`.`disabled`',
+ 'IFNULL(`value`.`disabled`, `default_value`.`disabled`)'
+ ]
+ ])
+ );
$this->select->expects($this->at(2))->method('joinLeft')->with(
['value' => $getTableReturnValue],
$quoteInfoReturnValue,
- [
- 'label',
- 'position',
- 'disabled'
- ]
+ []
)->willReturnSelf();
$this->select->expects($this->at(3))->method('joinLeft')->with(
['default_value' => $getTableReturnValue],
$quoteDefaultInfoReturnValue,
- ['label_default' => 'label', 'position_default' => 'position', 'disabled_default' => 'disabled']
+ []
)->willReturnSelf();
- $this->select->expects($this->at(4))->method('where')->with(
+ $this->select->expects($this->at(4))->method('columns')->with([
+ 'label' => 'IFNULL(`value`.`label`, `default_value`.`label`)',
+ 'position' => 'IFNULL(`value`.`position`, `default_value`.`position`)',
+ 'disabled' => 'IFNULL(`value`.`disabled`, `default_value`.`disabled`)',
+ 'label_default' => 'default_value.label',
+ 'position_default' => 'default_value.position',
+ 'disabled_default' => 'default_value.disabled'
+ ])->willReturnSelf();
+ $this->select->expects($this->at(5))->method('where')->with(
'main.attribute_id = ?',
$attributeId
)->willReturnSelf();
- $this->select->expects($this->at(5))->method('where')->with('main.disabled = 0')->willReturnSelf();
- $this->select->expects($this->at(7))->method('where')
+ $this->select->expects($this->at(6))->method('where')
+ ->with('main.disabled = 0')->willReturnSelf();
+ $this->select->expects($this->at(8))->method('where')
->with('entity.entity_id = ?', $productId)
->willReturnSelf();
$this->select->expects($this->once())->method('order')
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php
new file mode 100644
index 0000000000000..4fce12dc2de89
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php
@@ -0,0 +1,237 @@
+objectManager =
+ new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+ $this->connectionMock = $this->createMock(AdapterInterface::class);
+ $this->resourceMock = $this->createMock(ResourceConnection::class);
+ $this->resourceMock->method('getConnection')
+ ->willReturn($this->connectionMock);
+ $this->resourceMock->method('getTableName')
+ ->willReturnArgument(0);
+ $this->generatorMock = $this->createMock(Generator::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ protected function getVisibleImagesSelectMock(): MockObject
+ {
+ $selectMock = $this->getMockBuilder(Select::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $selectMock->expects($this->once())
+ ->method('distinct')
+ ->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('from')
+ ->with(
+ ['images' => Gallery::GALLERY_TABLE],
+ 'value as filepath'
+ )->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('where')
+ ->with('disabled = 0')
+ ->willReturnSelf();
+
+ return $selectMock;
+ }
+
+ /**
+ * @param int $imagesCount
+ * @dataProvider dataProvider
+ */
+ public function testGetCountAllProductImages(int $imagesCount): void
+ {
+ $selectMock = $this->getVisibleImagesSelectMock();
+ $selectMock->expects($this->exactly(2))
+ ->method('reset')
+ ->withConsecutive(
+ ['columns'],
+ ['distinct']
+ )->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('columns')
+ ->with(new \Zend_Db_Expr('count(distinct value)'))
+ ->willReturnSelf();
+
+ $this->connectionMock->expects($this->once())
+ ->method('select')
+ ->willReturn($selectMock);
+ $this->connectionMock->expects($this->once())
+ ->method('fetchOne')
+ ->with($selectMock)
+ ->willReturn($imagesCount);
+
+ $imageModel = $this->objectManager->getObject(
+ Image::class,
+ [
+ 'generator' => $this->generatorMock,
+ 'resourceConnection' => $this->resourceMock
+ ]
+ );
+
+ $this->assertSame(
+ $imagesCount,
+ $imageModel->getCountAllProductImages()
+ );
+ }
+
+ /**
+ * @param int $imagesCount
+ * @param int $batchSize
+ * @dataProvider dataProvider
+ */
+ public function testGetAllProductImages(
+ int $imagesCount,
+ int $batchSize
+ ): void {
+ $this->connectionMock->expects($this->once())
+ ->method('select')
+ ->willReturn($this->getVisibleImagesSelectMock());
+
+ $batchCount = (int)ceil($imagesCount / $batchSize);
+ $fetchResultsCallback = $this->getFetchResultCallbackForBatches($imagesCount, $batchSize);
+ $this->connectionMock->expects($this->exactly($batchCount))
+ ->method('fetchAll')
+ ->will($this->returnCallback($fetchResultsCallback));
+
+ /** @var Select | MockObject $selectMock */
+ $selectMock = $this->getMockBuilder(Select::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->generatorMock->expects($this->once())
+ ->method('generate')
+ ->with(
+ 'value_id',
+ $selectMock,
+ $batchSize,
+ BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR
+ )->will(
+ $this->returnCallback(
+ $this->getBatchIteratorCallback($selectMock, $batchCount)
+ )
+ );
+
+ $imageModel = $this->objectManager->getObject(
+ Image::class,
+ [
+ 'generator' => $this->generatorMock,
+ 'resourceConnection' => $this->resourceMock,
+ 'batchSize' => $batchSize
+ ]
+ );
+
+ $this->assertCount($imagesCount, $imageModel->getAllProductImages());
+ }
+
+ /**
+ * @param int $imagesCount
+ * @param int $batchSize
+ * @return \Closure
+ */
+ protected function getFetchResultCallbackForBatches(
+ int $imagesCount,
+ int $batchSize
+ ): \Closure {
+ $fetchResultsCallback = function () use (&$imagesCount, $batchSize) {
+ $batchSize =
+ ($imagesCount >= $batchSize) ? $batchSize : $imagesCount;
+ $imagesCount -= $batchSize;
+
+ $getFetchResults = function ($batchSize): array {
+ $result = [];
+ $count = $batchSize;
+ while ($count) {
+ $count--;
+ $result[$count] = $count;
+ }
+
+ return $result;
+ };
+
+ return $getFetchResults($batchSize);
+ };
+
+ return $fetchResultsCallback;
+ }
+
+ /**
+ * @param Select | MockObject $selectMock
+ * @param int $batchCount
+ * @return \Closure
+ */
+ protected function getBatchIteratorCallback(
+ MockObject $selectMock,
+ int $batchCount
+ ): \Closure {
+ $iteratorCallback = function () use ($batchCount, $selectMock): array {
+ $result = [];
+ $count = $batchCount;
+ while ($count) {
+ $count--;
+ $result[$count] = $selectMock;
+ }
+
+ return $result;
+ };
+
+ return $iteratorCallback;
+ }
+
+ /**
+ * Data Provider
+ * @return array
+ */
+ public function dataProvider(): array
+ {
+ return [
+ [300, 300],
+ [300, 100],
+ [139, 100],
+ [67, 10],
+ [154, 47],
+ [0, 100]
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimatorTest.php
index 1c47644338143..728044b89cafe 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimatorTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimatorTest.php
@@ -49,7 +49,6 @@ protected function setUp()
public function testEstimateRowSize()
{
- $this->markTestSkipped('Unskip after MAGETWO-89738');
$expectedResult = 40000000;
$maxRelatedProductCount = 10;
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 12bc9acfa4c51..009cd690d4cd4 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/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
index d84f496e81915..7379600011bcf 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/Form/Modifier/General.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
index 98de8ea347671..6ec1cc6c46d9d 100755
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
@@ -58,8 +58,11 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * Customize number fields for advanced price and weight fields.
+ *
* @since 101.0.0
+ * @param array $data
+ * @return array
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function modifyData(array $data)
@@ -130,8 +133,11 @@ protected function customizeAdvancedPriceFormat(array $data)
}
/**
- * {@inheritdoc}
+ * Customize product form fields.
+ *
* @since 101.0.0
+ * @param array $meta
+ * @return array
*/
public function modifyMeta(array $meta)
{
@@ -361,7 +367,8 @@ protected function customizeNameListeners(array $meta)
$skuPath . static::META_CONFIG_PATH,
$meta,
[
- 'autoImportIfEmpty' => true
+ 'autoImportIfEmpty' => true,
+ 'validation' => ['no-marginal-whitespace' => true]
]
);
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/TierPrice.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/TierPrice.php
index 0eddca3322205..a529580e29239 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/TierPrice.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/TierPrice.php
@@ -45,7 +45,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.1.0
*/
public function modifyData(array $data)
@@ -54,8 +54,11 @@ public function modifyData(array $data)
}
/**
- * {@inheritdoc}
+ * Add tier price info to meta array.
+ *
* @since 101.1.0
+ * @param array $meta
+ * @return array
*/
public function modifyMeta(array $meta)
{
@@ -150,8 +153,8 @@ private function getUpdatedTierPriceStructure(array $priceMeta)
'dataType' => Price::NAME,
'addbefore' => '%',
'validation' => [
- 'validate-number' => true,
- 'less-than-equals-to' => 100
+ 'required-entry' => true,
+ 'validate-positive-percent-decimal' => true
],
'visible' => $firstOption
&& $firstOption['value'] == ProductPriceOptionsInterface::VALUE_PERCENT,
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 216bc16968fcb..524927ac1c4b4 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/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml
index d6c98b92596fd..7a05601fcd666 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/system.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml
@@ -10,6 +10,9 @@
Catalog
+
+ Advanced
+
separator-top
Catalog
@@ -93,6 +96,11 @@
Whether to show "All" option in the "Show X Per Page" dropdown
Magento\Config\Model\Config\Source\Yesno
+
Product Image Placeholders
@@ -194,5 +202,19 @@
+
+ separator-top
+ System
+ advanced
+ Magento_Config::config_system
+
+ Images Upload Configuration
+
+ Quality
+ validate-digits validate-digits-range digits-range-1-100 required-entry
+ Jpeg quality for resized images 1-100%.
+
+
+
diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml
index f52760aa50743..3a842166a3825 100644
--- a/app/code/Magento/Catalog/etc/config.xml
+++ b/app/code/Magento/Catalog/etc/config.xml
@@ -30,6 +30,7 @@
0
position
1
+ 0
@@ -66,6 +67,9 @@
custom_options
+
+ 80
+
diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml
index 860e5fee9f157..17e3dddc41c3b 100644
--- a/app/code/Magento/Catalog/etc/db_schema.xml
+++ b/app/code/Magento/Catalog/etc/db_schema.xml
@@ -22,13 +22,13 @@
comment="Creation Time"/>
-
+
-
+
-
+
@@ -43,27 +43,27 @@
-
+
-
-
-
-
+
-
+
-
+
@@ -79,27 +79,27 @@
default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -115,27 +115,27 @@
default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -150,27 +150,27 @@
-
+
-
-
-
-
+
-
+
-
+
@@ -185,27 +185,27 @@
-
+
-
-
-
-
+
-
+
-
+
@@ -222,30 +222,30 @@
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -267,13 +267,13 @@
comment="Tree Level"/>
-
+
-
+
-
+
@@ -288,30 +288,30 @@
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -327,30 +327,30 @@
default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -366,30 +366,30 @@
default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -404,30 +404,30 @@
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -442,30 +442,30 @@
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -479,22 +479,22 @@
default="0" comment="Product ID"/>
-
+
-
-
-
+
-
+
@@ -512,18 +512,18 @@
default="0" comment="Store ID"/>
-
+
-
+
-
+
@@ -542,29 +542,29 @@
default="0" comment="Product ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -574,17 +574,17 @@
comment="Product ID"/>
-
+
-
-
-
+
@@ -593,7 +593,7 @@
-
+
@@ -607,27 +607,27 @@
default="0" comment="Linked Product ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -640,13 +640,13 @@
-
+
-
-
+
@@ -660,21 +660,21 @@
comment="Link ID"/>
-
+
-
-
-
+
-
+
@@ -688,21 +688,21 @@
comment="Link ID"/>
-
+
-
-
-
+
-
+
@@ -715,21 +715,21 @@
-
+
-
-
-
+
-
+
@@ -751,29 +751,29 @@
comment="Website ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -788,13 +788,13 @@
comment="Media entry type"/>
-
+
-
-
+
@@ -813,28 +813,33 @@
default="0" comment="Is Disabled"/>
-
+
-
-
-
-
+
-
+
-
+
+
+
+
+
+
@@ -875,20 +880,20 @@
-
+
-
-
-
+
-
+
@@ -901,20 +906,20 @@
-
+
-
-
-
+
-
+
@@ -927,13 +932,13 @@
-
+
-
-
+
@@ -948,21 +953,21 @@
-
+
-
-
-
+
-
+
@@ -975,21 +980,21 @@
-
+
-
-
-
+
-
+
@@ -1037,16 +1042,16 @@
identity="false" default="0" comment="Is Visible in Grid"/>
-
+
-
-
+
-
+
@@ -1055,17 +1060,17 @@
comment="Parent ID"/>
-
+
-
-
-
+
@@ -1081,20 +1086,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1110,20 +1115,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1147,18 +1152,18 @@
comment="Max Price"/>
-
+
-
+
-
+
-
+
@@ -1174,24 +1179,24 @@
comment="Website ID"/>
-
+
-
-
-
-
+
-
+
@@ -1203,12 +1208,12 @@
comment="Default store id for website"/>
-
+
-
-
+
@@ -1226,7 +1231,7 @@
comment="Price"/>
-
+
@@ -1247,7 +1252,7 @@
comment="Price"/>
-
+
@@ -1268,7 +1273,7 @@
comment="Max Price"/>
-
+
@@ -1288,7 +1293,7 @@
comment="Max Price"/>
-
+
@@ -1316,7 +1321,7 @@
comment="Tier Price"/>
-
+
@@ -1344,7 +1349,7 @@
comment="Tier Price"/>
-
+
@@ -1364,7 +1369,7 @@
comment="Max Price"/>
-
+
@@ -1384,7 +1389,7 @@
comment="Max Price"/>
-
+
@@ -1406,7 +1411,7 @@
comment="Max Price"/>
-
+
@@ -1429,7 +1434,7 @@
comment="Max Price"/>
-
+
@@ -1448,20 +1453,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1477,20 +1482,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1506,20 +1511,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1535,20 +1540,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1572,18 +1577,18 @@
comment="Max Price"/>
-
+
-
+
-
+
-
+
@@ -1607,18 +1612,18 @@
comment="Max Price"/>
-
+
-
+
-
+
-
+
@@ -1636,12 +1641,12 @@
default="0" comment="Store ID"/>
-
+
-
+
@@ -1653,14 +1658,14 @@
comment="Value media Entry ID"/>
-
-
-
+
@@ -1677,20 +1682,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1706,20 +1711,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1743,18 +1748,18 @@
comment="Max Price"/>
-
+
-
+