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 f9f43cebc592..119e7a35747c 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) {
@@ -124,11 +134,9 @@ define([
process: [{
action: 'load',
fileTypes: /^image\/(gif|jpeg|png)$/
- }, {
- action: 'resize',
- maxWidth: this.options.maxWidth,
- maxHeight: this.options.maxHeight
- }, {
+ },
+ 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 0a692a9b868c..c2a0d4dab1fb 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/Backend/view/adminhtml/web/template/dynamic-rows/grid.html b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html
index fe30ca7e83f1..0033f4c071e4 100644
--- a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html
+++ b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html
@@ -69,7 +69,7 @@
* @api
* @since 100.0.2
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
*/
-abstract class Index extends \Magento\Backend\App\Action
+abstract class Index extends Action
{
/**
* 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 +53,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 +65,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 +73,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/Create.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php
index 53f45aff50cb..ee5a56e81483 100644
--- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php
+++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Create.php
@@ -1,15 +1,18 @@
_coreRegistry->register('backup_manager', $backupManager);
if ($this->getRequest()->getParam('maintenance_mode')) {
- if (!$this->maintenanceMode->set(true)) {
+ $this->maintenanceMode->set(true);
+
+ if (!$this->maintenanceMode->isOn()) {
$response->setError(
__(
'You need more permissions to activate maintenance mode right now.'
diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Disabled.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Disabled.php
new file mode 100644
index 000000000000..f6fe430ae083
--- /dev/null
+++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Disabled.php
@@ -0,0 +1,48 @@
+pageFactory = $pageFactory;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function execute()
+ {
+ return $this->pageFactory->create();
+ }
+}
diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php
index 3bbda65cb4cf..271e3713034d 100644
--- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php
+++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Index.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Backup\Controller\Adminhtml\Index;
-class Index extends \Magento\Backup\Controller\Adminhtml\Index
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Index extends \Magento\Backup\Controller\Adminhtml\Index implements HttpGetActionInterface
{
/**
* Backup list action
diff --git a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php
index 0451f6ed09bd..7f450e7e313c 100644
--- a/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php
+++ b/app/code/Magento/Backup/Controller/Adminhtml/Index/Rollback.php
@@ -6,13 +6,16 @@
*/
namespace Magento\Backup\Controller\Adminhtml\Index;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Filesystem;
/**
+ * Backup rollback controller.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Rollback extends \Magento\Backup\Controller\Adminhtml\Index
+class Rollback extends \Magento\Backup\Controller\Adminhtml\Index implements HttpPostActionInterface
{
/**
* Rollback Action
@@ -82,7 +85,9 @@ public function execute()
}
if ($this->getRequest()->getParam('maintenance_mode')) {
- if (!$this->maintenanceMode->set(true)) {
+ $this->maintenanceMode->set(true);
+
+ if (!$this->maintenanceMode->isOn()) {
$response->setError(
__(
'You need more permissions to activate maintenance mode right now.'
@@ -122,6 +127,7 @@ public function execute()
$adminSession->destroy();
$response->setRedirectUrl($this->getUrl('*'));
+ // phpcs:disable Magento2.Exceptions.ThrowCatch
} catch (\Magento\Framework\Backup\Exception\CantLoadSnapshot $e) {
$errorMsg = __('We can\'t find the backup file.');
} catch (\Magento\Framework\Backup\Exception\FtpConnectionFailed $e) {
diff --git a/app/code/Magento/Backup/Cron/SystemBackup.php b/app/code/Magento/Backup/Cron/SystemBackup.php
index 750262ab1c14..9502377a39d0 100644
--- a/app/code/Magento/Backup/Cron/SystemBackup.php
+++ b/app/code/Magento/Backup/Cron/SystemBackup.php
@@ -8,6 +8,9 @@
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Store\Model\ScopeInterface;
+/**
+ * Performs scheduled backup.
+ */
class SystemBackup
{
const XML_PATH_BACKUP_ENABLED = 'system/backup/enabled';
@@ -101,6 +104,10 @@ public function __construct(
*/
public function execute()
{
+ if (!$this->_backupData->isEnabled()) {
+ return $this;
+ }
+
if (!$this->_scopeConfig->isSetFlag(self::XML_PATH_BACKUP_ENABLED, ScopeInterface::SCOPE_STORE)) {
return $this;
}
diff --git a/app/code/Magento/Backup/Helper/Data.php b/app/code/Magento/Backup/Helper/Data.php
index 3d60bf9d9c9c..c6df6a736685 100644
--- a/app/code/Magento/Backup/Helper/Data.php
+++ b/app/code/Magento/Backup/Helper/Data.php
@@ -3,6 +3,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Backup\Helper;
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -110,7 +113,7 @@ public function getBackupsDir()
public function getExtensionByType($type)
{
$extensions = $this->getExtensions();
- return isset($extensions[$type]) ? $extensions[$type] : '';
+ return $extensions[$type] ?? '';
}
/**
@@ -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/Backup.php b/app/code/Magento/Backup/Model/Backup.php
index 3768f2bf8c8c..c3507ecf5b45 100644
--- a/app/code/Magento/Backup/Model/Backup.php
+++ b/app/code/Magento/Backup/Model/Backup.php
@@ -14,6 +14,7 @@
* @method string getPath()
* @method string getName()
* @method string getTime()
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @api
* @since 100.0.2
@@ -80,6 +81,7 @@ class Backup extends \Magento\Framework\DataObject implements \Magento\Framework
* @param \Magento\Framework\Encryption\EncryptorInterface $encryptor
* @param \Magento\Framework\Filesystem $filesystem
* @param array $data
+ * @throws \Magento\Framework\Exception\FileSystemException
*/
public function __construct(
\Magento\Backup\Helper\Data $helper,
@@ -242,7 +244,7 @@ public function setFile(&$content)
/**
* Return content of backup file
*
- * @return string
+ * @return array
* @throws \Magento\Framework\Exception\LocalizedException
*/
public function &getFile()
@@ -275,8 +277,9 @@ public function deleteFile()
*
* @param bool $write
* @return $this
- * @throws \Magento\Framework\Exception\InputException
* @throws \Magento\Framework\Backup\Exception\NotEnoughPermissions
+ * @throws \Magento\Framework\Exception\FileSystemException
+ * @throws \Magento\Framework\Exception\InputException
*/
public function open($write = false)
{
@@ -330,6 +333,7 @@ protected function _getStream()
*
* @param int $length
* @return string
+ * @throws \Magento\Framework\Exception\InputException
*/
public function read($length)
{
@@ -340,6 +344,7 @@ public function read($length)
* Check end of file.
*
* @return bool
+ * @throws \Magento\Framework\Exception\InputException
*/
public function eof()
{
@@ -370,6 +375,7 @@ public function write($string)
* Close open backup file
*
* @return $this
+ * @throws \Magento\Framework\Exception\InputException
*/
public function close()
{
@@ -383,6 +389,8 @@ public function close()
* Print output
*
* @return string
+ * @return \Magento\Framework\Filesystem\Directory\ReadInterface|string|void
+ * @throws \Magento\Framework\Exception\FileSystemException
*/
public function output()
{
@@ -398,6 +406,8 @@ public function output()
}
/**
+ * Get Size
+ *
* @return int|mixed
*/
public function getSize()
@@ -419,6 +429,7 @@ public function getSize()
*
* @param string $password
* @return bool
+ * @throws \Exception
*/
public function validateUserPassword($password)
{
diff --git a/app/code/Magento/Backup/Model/Config/Backend/Cron.php b/app/code/Magento/Backup/Model/Config/Backend/Cron.php
index 4855ef112950..2f0e4069f049 100644
--- a/app/code/Magento/Backup/Model/Config/Backend/Cron.php
+++ b/app/code/Magento/Backup/Model/Config/Backend/Cron.php
@@ -76,8 +76,8 @@ public function afterSave()
if ($enabled) {
$cronExprArray = [
- intval($time[1]), # Minute
- intval($time[0]), # Hour
+ (int) $time[1], # Minute
+ (int) $time[0], # Hour
$frequency == $frequencyMonthly ? '1' : '*', # Day of the Month
'*', # Month of the Year
$frequency == $frequencyWeekly ? '1' : '*', # Day of the Week
diff --git a/app/code/Magento/Backup/Model/Db.php b/app/code/Magento/Backup/Model/Db.php
index 8fbd5da1c984..084b35448a82 100644
--- a/app/code/Magento/Backup/Model/Db.php
+++ b/app/code/Magento/Backup/Model/Db.php
@@ -5,11 +5,18 @@
*/
namespace Magento\Backup\Model;
+use Magento\Backup\Helper\Data as Helper;
+use Magento\Backup\Model\ResourceModel\Table\GetListTables;
+use Magento\Backup\Model\ResourceModel\View\CreateViewsBackup;
+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
{
@@ -34,15 +41,40 @@ class Db implements \Magento\Framework\Backup\Db\BackupDbInterface
protected $_resource = null;
/**
- * @param \Magento\Backup\Model\ResourceModel\Db $resourceDb
+ * @var Helper
+ */
+ private $helper;
+
+ /**
+ * @var GetListTables
+ */
+ private $getListTables;
+
+ /**
+ * @var CreateViewsBackup
+ */
+ private $getViewsBackup;
+
+ /**
+ * Db constructor.
+ * @param ResourceModel\Db $resourceDb
* @param \Magento\Framework\App\ResourceConnection $resource
+ * @param Helper|null $helper
+ * @param GetListTables|null $getListTables
+ * @param CreateViewsBackup|null $getViewsBackup
*/
public function __construct(
- \Magento\Backup\Model\ResourceModel\Db $resourceDb,
- \Magento\Framework\App\ResourceConnection $resource
+ ResourceModel\Db $resourceDb,
+ \Magento\Framework\App\ResourceConnection $resource,
+ ?Helper $helper = null,
+ ?GetListTables $getListTables = null,
+ ?CreateViewsBackup $getViewsBackup = null
) {
$this->_resourceDb = $resourceDb;
$this->_resource = $resource;
+ $this->helper = $helper ?? ObjectManager::getInstance()->get(Helper::class);
+ $this->getListTables = $getListTables ?? ObjectManager::getInstance()->get(GetListTables::class);
+ $this->getViewsBackup = $getViewsBackup ?? ObjectManager::getInstance()->get(CreateViewsBackup::class);
}
/**
@@ -63,6 +95,8 @@ public function getResource()
}
/**
+ * Tables list.
+ *
* @return array
*/
public function getTables()
@@ -71,6 +105,8 @@ public function getTables()
}
/**
+ * Command to recreate given table.
+ *
* @param string $tableName
* @param bool $addDropIfExists
* @return string
@@ -81,6 +117,8 @@ public function getTableCreateScript($tableName, $addDropIfExists = false)
}
/**
+ * Generate table's data dump.
+ *
* @param string $tableName
* @return string
*/
@@ -90,6 +128,8 @@ public function getTableDataDump($tableName)
}
/**
+ * Header for dumps.
+ *
* @return string
*/
public function getHeader()
@@ -98,6 +138,8 @@ public function getHeader()
}
/**
+ * Footer for dumps.
+ *
* @return string
*/
public function getFooter()
@@ -106,6 +148,8 @@ public function getFooter()
}
/**
+ * Get backup SQL.
+ *
* @return string
*/
public function renderSql()
@@ -124,18 +168,19 @@ 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();
- $tables = $this->getResource()->getTables();
+ $tables = $this->getListTables->execute();
$backup->write($this->getResource()->getHeader());
@@ -172,6 +217,8 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu
$backup->write($this->getResource()->getTableDataAfterSql($table));
}
}
+ $this->getViewsBackup->execute($backup);
+
$backup->write($this->getResource()->getTableForeignKeysSql());
$backup->write($this->getResource()->getTableTriggersSql());
$backup->write($this->getResource()->getFooter());
@@ -179,8 +226,6 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu
$this->getResource()->commitTransaction();
$backup->close();
-
- return $this;
}
/**
diff --git a/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php b/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php
new file mode 100644
index 000000000000..73c4221feba3
--- /dev/null
+++ b/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php
@@ -0,0 +1,44 @@
+resource = $resource;
+ }
+
+ /**
+ * Get list of database tables excluding views.
+ *
+ * @return array
+ */
+ public function execute(): array
+ {
+ return $this->resource->getConnection('backup')->fetchCol(
+ "SHOW FULL TABLES WHERE `Table_type` = ?",
+ self::TABLE_TYPE
+ );
+ }
+}
diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php b/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php
new file mode 100644
index 000000000000..51b49dcb9e48
--- /dev/null
+++ b/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php
@@ -0,0 +1,116 @@
+getListViews = $getListViews;
+ $this->resourceConnection = $resourceConnection;
+ }
+
+ /**
+ * Write backup data to backup file.
+ *
+ * @param BackupInterface $backup
+ */
+ public function execute(BackupInterface $backup): void
+ {
+ $views = $this->getListViews->execute();
+
+ foreach ($views as $view) {
+ $backup->write($this->getViewHeader($view));
+ $backup->write($this->getDropViewSql($view));
+ $backup->write($this->getCreateView($view));
+ }
+ }
+
+ /**
+ * Retrieve Database connection for Backup.
+ *
+ * @return AdapterInterface
+ */
+ private function getConnection(): AdapterInterface
+ {
+ if (!$this->connection) {
+ $this->connection = $this->resourceConnection->getConnection('backup');
+ }
+
+ return $this->connection;
+ }
+
+ /**
+ * Get CREATE VIEW query for the specific view.
+ *
+ * @param string $viewName
+ * @return string
+ */
+ private function getCreateView(string $viewName): string
+ {
+ $quotedViewName = $this->getConnection()->quoteIdentifier($viewName);
+ $query = 'SHOW CREATE VIEW ' . $quotedViewName;
+ $row = $this->getConnection()->fetchRow($query);
+ $regExp = '/\sDEFINER\=\`([^`]*)\`\@\`([^`]*)\`/';
+ $sql = preg_replace($regExp, '', $row['Create View']);
+
+ return $sql . ';' . "\n";
+ }
+
+ /**
+ * Prepare a header for View being dumped.
+ *
+ * @param string $viewName
+ * @return string
+ */
+ public function getViewHeader(string $viewName): string
+ {
+ $quotedViewName = $this->getConnection()->quoteIdentifier($viewName);
+ return "\n--\n" . "-- Structure for view {$quotedViewName}\n" . "--\n\n";
+ }
+
+ /**
+ * Make sure that View being created is deleted if already exists.
+ *
+ * @param string $viewName
+ * @return string
+ */
+ public function getDropViewSql(string $viewName): string
+ {
+ $quotedViewName = $this->getConnection()->quoteIdentifier($viewName);
+ return sprintf('DROP VIEW IF EXISTS %s;\n', $quotedViewName);
+ }
+}
diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php b/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php
new file mode 100644
index 000000000000..c76ea2842180
--- /dev/null
+++ b/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php
@@ -0,0 +1,44 @@
+resource = $resource;
+ }
+
+ /**
+ * Get list of database views.
+ *
+ * @return array
+ */
+ public function execute(): array
+ {
+ return $this->resource->getConnection('backup')->fetchCol(
+ "SHOW FULL TABLES WHERE `Table_type` = ?",
+ self::TABLE_TYPE
+ );
+ }
+}
diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
index 4f34f24c3a80..ebc4ac1fb056 100644
--- a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
+++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
@@ -17,9 +17,10 @@
+
+
-
diff --git a/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml b/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml
index ae97351cafca..ad218cdd5750 100644
--- a/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml
+++ b/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml
@@ -20,4 +20,8 @@
databaseBackup
Database
-
+
+ WebSetupWizard
+ Database
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml
index b83434f7ad1d..26f8817c0a1b 100644
--- a/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml
+++ b/app/code/Magento/Backup/Test/Mftf/Test/AdminCreateAndDeleteBackupsTest.xml
@@ -17,7 +17,9 @@
-
+
+
+
diff --git a/app/code/Magento/Backup/Test/Unit/Cron/SystemBackupTest.php b/app/code/Magento/Backup/Test/Unit/Cron/SystemBackupTest.php
index b7dfb30c0a1b..56a7ef42a0bc 100644
--- a/app/code/Magento/Backup/Test/Unit/Cron/SystemBackupTest.php
+++ b/app/code/Magento/Backup/Test/Unit/Cron/SystemBackupTest.php
@@ -3,134 +3,44 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Backup\Test\Unit\Cron;
+use Magento\Backup\Cron\SystemBackup;
+use PHPUnit\Framework\TestCase;
+use Magento\Backup\Helper\Data as Helper;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-class SystemBackupTest extends \PHPUnit\Framework\TestCase
+class SystemBackupTest extends TestCase
{
/**
- * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
- */
- private $objectManager;
-
- /**
- * @var \Magento\Backup\Cron\SystemBackup
- */
- private $systemBackup;
-
- /**
- * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $scopeConfigMock;
-
- /**
- * @var \Magento\Backup\Helper\Data|\PHPUnit_Framework_MockObject_MockObject
- */
- private $backupDataMock;
-
- /**
- * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject
- */
- private $registryMock;
-
- /**
- * @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var Helper|\PHPUnit_Framework_MockObject_MockObject
*/
- private $loggerMock;
+ private $helperMock;
/**
- * Filesystem facade
- *
- * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject
+ * @var SystemBackup
*/
- private $filesystemMock;
+ private $cron;
/**
- * @var \Magento\Framework\Backup\Factory|\PHPUnit_Framework_MockObject_MockObject
+ * @inheritDoc
*/
- private $backupFactoryMock;
-
- /**
- * @var \Magento\Framework\App\MaintenanceMode|\PHPUnit_Framework_MockObject_MockObject
- */
- private $maintenanceModeMock;
-
- /**
- * @var \Magento\Framework\Backup\Db|\PHPUnit_Framework_MockObject_MockObject
- */
- private $backupDbMock;
-
- /**
- * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $objectManagerMock;
-
protected function setUp()
{
- $this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class)
- ->getMock();
- $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
- ->getMock();
- $this->backupDataMock = $this->getMockBuilder(\Magento\Backup\Helper\Data::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->registryMock = $this->getMockBuilder(\Magento\Framework\Registry::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
- ->getMock();
- $this->filesystemMock = $this->getMockBuilder(\Magento\Framework\Filesystem::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->backupFactoryMock = $this->getMockBuilder(\Magento\Framework\Backup\Factory::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->maintenanceModeMock = $this->getMockBuilder(\Magento\Framework\App\MaintenanceMode::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->backupDbMock = $this->getMockBuilder(\Magento\Framework\Backup\Db::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->backupDbMock->expects($this->any())->method('setBackupExtension')->willReturnSelf();
- $this->backupDbMock->expects($this->any())->method('setTime')->willReturnSelf();
- $this->backupDbMock->expects($this->any())->method('setBackupsDir')->willReturnSelf();
-
- $this->objectManager = new ObjectManager($this);
- $this->systemBackup = $this->objectManager->getObject(
- \Magento\Backup\Cron\SystemBackup::class,
- [
- 'backupData' => $this->backupDataMock,
- 'coreRegistry' => $this->registryMock,
- 'logger' => $this->loggerMock,
- 'scopeConfig' => $this->scopeConfigMock,
- 'filesystem' => $this->filesystemMock,
- 'backupFactory' => $this->backupFactoryMock,
- 'maintenanceMode' => $this->maintenanceModeMock,
- ]
- );
+ $objectManager = new ObjectManager($this);
+ $this->helperMock = $this->getMockBuilder(Helper::class)->disableOriginalConstructor()->getMock();
+ $this->cron = $objectManager->getObject(SystemBackup::class, ['backupData' => $this->helperMock]);
}
/**
- * @expectedException \Exception
+ * Test that cron doesn't do anything if backups are disabled.
*/
- public function testExecuteThrowsException()
+ public function testDisabled()
{
- $type = 'db';
- $this->scopeConfigMock->expects($this->any())->method('isSetFlag')->willReturn(true);
-
- $this->scopeConfigMock->expects($this->once())->method('getValue')
- ->with('system/backup/type', 'store')
- ->willReturn($type);
-
- $this->backupFactoryMock->expects($this->once())->method('create')->willReturn($this->backupDbMock);
-
- $this->backupDbMock->expects($this->once())->method('create')->willThrowException(new \Exception);
-
- $this->backupDataMock->expects($this->never())->method('getCreateSuccessMessageByType')->with($type);
- $this->loggerMock->expects($this->never())->method('info');
-
- $this->systemBackup->execute();
+ $this->helperMock->expects($this->any())->method('isEnabled')->willReturn(false);
+ $this->cron->execute();
}
}
diff --git a/app/code/Magento/Backup/Test/Unit/Model/DbTest.php b/app/code/Magento/Backup/Test/Unit/Model/DbTest.php
deleted file mode 100644
index 0cab5f0ad1e9..000000000000
--- a/app/code/Magento/Backup/Test/Unit/Model/DbTest.php
+++ /dev/null
@@ -1,243 +0,0 @@
-dbResourceMock = $this->getMockBuilder(DbResource::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->connectionResourceMock = $this->getMockBuilder(ResourceConnection::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->objectManager = new ObjectManager($this);
- $this->dbModel = $this->objectManager->getObject(
- Db::class,
- [
- 'resourceDb' => $this->dbResourceMock,
- 'resource' => $this->connectionResourceMock
- ]
- );
- }
-
- public function testGetResource()
- {
- self::assertEquals($this->dbResourceMock, $this->dbModel->getResource());
- }
-
- public function testGetTables()
- {
- $tables = [];
- $this->dbResourceMock->expects($this->once())
- ->method('getTables')
- ->willReturn($tables);
-
- self::assertEquals($tables, $this->dbModel->getTables());
- }
-
- public function testGetTableCreateScript()
- {
- $tableName = 'some_table';
- $script = 'script';
- $this->dbResourceMock->expects($this->once())
- ->method('getTableCreateScript')
- ->with($tableName, false)
- ->willReturn($script);
-
- self::assertEquals($script, $this->dbModel->getTableCreateScript($tableName, false));
- }
-
- public function testGetTableDataDump()
- {
- $tableName = 'some_table';
- $dump = 'dump';
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDataDump')
- ->with($tableName)
- ->willReturn($dump);
-
- self::assertEquals($dump, $this->dbModel->getTableDataDump($tableName));
- }
-
- public function testGetHeader()
- {
- $header = 'header';
- $this->dbResourceMock->expects($this->once())
- ->method('getHeader')
- ->willReturn($header);
-
- self::assertEquals($header, $this->dbModel->getHeader());
- }
-
- public function testGetFooter()
- {
- $footer = 'footer';
- $this->dbResourceMock->expects($this->once())
- ->method('getFooter')
- ->willReturn($footer);
-
- self::assertEquals($footer, $this->dbModel->getFooter());
- }
-
- public function testRenderSql()
- {
- $header = 'header';
- $script = 'script';
- $tableName = 'some_table';
- $tables = [$tableName, $tableName];
- $dump = 'dump';
- $footer = 'footer';
-
- $this->dbResourceMock->expects($this->once())
- ->method('getTables')
- ->willReturn($tables);
- $this->dbResourceMock->expects($this->once())
- ->method('getHeader')
- ->willReturn($header);
- $this->dbResourceMock->expects($this->exactly(2))
- ->method('getTableCreateScript')
- ->with($tableName, true)
- ->willReturn($script);
- $this->dbResourceMock->expects($this->exactly(2))
- ->method('getTableDataDump')
- ->with($tableName)
- ->willReturn($dump);
- $this->dbResourceMock->expects($this->once())
- ->method('getFooter')
- ->willReturn($footer);
-
- self::assertEquals(
- $header . $script . $dump . $script . $dump . $footer,
- $this->dbModel->renderSql()
- );
- }
-
- public function testCreateBackup()
- {
- /** @var BackupInterface|\PHPUnit_Framework_MockObject_MockObject $backupMock */
- $backupMock = $this->getMockBuilder(BackupInterface::class)->getMock();
- /** @var DataObject $tableStatus */
- $tableStatus = new DataObject();
-
- $tableName = 'some_table';
- $tables = [$tableName];
- $header = 'header';
- $footer = 'footer';
- $dropSql = 'drop_sql';
- $createSql = 'create_sql';
- $beforeSql = 'before_sql';
- $afterSql = 'after_sql';
- $dataSql = 'data_sql';
- $foreignKeysSql = 'foreign_keys';
- $triggersSql = 'triggers_sql';
- $rowsCount = 2;
- $dataLength = 1;
-
- $this->dbResourceMock->expects($this->once())
- ->method('beginTransaction');
- $this->dbResourceMock->expects($this->once())
- ->method('commitTransaction');
- $this->dbResourceMock->expects($this->once())
- ->method('getTables')
- ->willReturn($tables);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDropSql')
- ->willReturn($dropSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableCreateSql')
- ->with($tableName, false)
- ->willReturn($createSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDataBeforeSql')
- ->with($tableName)
- ->willReturn($beforeSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDataAfterSql')
- ->with($tableName)
- ->willReturn($afterSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableDataSql')
- ->with($tableName, $rowsCount, 0)
- ->willReturn($dataSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableStatus')
- ->with($tableName)
- ->willReturn($tableStatus);
- $this->dbResourceMock->expects($this->once())
- ->method('getTables')
- ->willReturn($createSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getHeader')
- ->willReturn($header);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableHeader')
- ->willReturn($header);
- $this->dbResourceMock->expects($this->once())
- ->method('getFooter')
- ->willReturn($footer);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableForeignKeysSql')
- ->willReturn($foreignKeysSql);
- $this->dbResourceMock->expects($this->once())
- ->method('getTableTriggersSql')
- ->willReturn($triggersSql);
- $backupMock->expects($this->once())
- ->method('open');
- $backupMock->expects($this->once())
- ->method('close');
-
- $tableStatus->setRows($rowsCount);
- $tableStatus->setDataLength($dataLength);
-
- $backupMock->expects($this->any())
- ->method('write')
- ->withConsecutive(
- [$this->equalTo($header)],
- [$this->equalTo($header . $dropSql . "\n")],
- [$this->equalTo($createSql . "\n")],
- [$this->equalTo($beforeSql)],
- [$this->equalTo($dataSql)],
- [$this->equalTo($afterSql)],
- [$this->equalTo($foreignKeysSql)],
- [$this->equalTo($triggersSql)],
- [$this->equalTo($footer)]
- );
-
- $this->dbModel->createBackup($backupMock);
- }
-}
diff --git a/app/code/Magento/Backup/etc/adminhtml/system.xml b/app/code/Magento/Backup/etc/adminhtml/system.xml
index 4028452d0443..aa6635b4dde4 100644
--- a/app/code/Magento/Backup/etc/adminhtml/system.xml
+++ b/app/code/Magento/Backup/etc/adminhtml/system.xml
@@ -9,15 +9,24 @@
-
+
+
+
+ Disabled by default for security reasons
+ Magento\Config\Model\Config\Source\Yesno
+
Magento\Config\Model\Config\Source\Yesno
+
+ 1
+
-
+
1
+ 1
Magento\Backup\Model\Config\Source\Type
@@ -25,12 +34,14 @@
1
+ 1
1
+ 1
Magento\Cron\Model\Config\Source\Frequency
Magento\Backup\Model\Config\Backend\Cron
@@ -40,6 +51,7 @@
Please put your store into maintenance mode during backup.
1
+ 1
Magento\Config\Model\Config\Source\Yesno
diff --git a/app/code/Magento/Backup/etc/config.xml b/app/code/Magento/Backup/etc/config.xml
new file mode 100644
index 000000000000..fb0808983b9c
--- /dev/null
+++ b/app/code/Magento/Backup/etc/config.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ 0
+
+
+
+
diff --git a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_block.xml b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_block.xml
index e17618b97e21..e3e984d933f2 100644
--- a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_block.xml
+++ b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_block.xml
@@ -11,7 +11,7 @@
backupsGrid
- Magento\Backup\Model\Fs\Collection
+ Magento\Backup\Model\Fs\Collection
time
desc
diff --git a/app/code/Magento/Backup/view/adminhtml/layout/backup_index_disabled.xml b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_disabled.xml
new file mode 100644
index 000000000000..3470f528e5ce
--- /dev/null
+++ b/app/code/Magento/Backup/view/adminhtml/layout/backup_index_disabled.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Backup functionality is disabled
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Backup/view/adminhtml/templates/backup/disabled.phtml b/app/code/Magento/Backup/view/adminhtml/templates/backup/disabled.phtml
new file mode 100644
index 000000000000..a5308dce5cc5
--- /dev/null
+++ b/app/code/Magento/Backup/view/adminhtml/templates/backup/disabled.phtml
@@ -0,0 +1,7 @@
+
+Backup functionality is currently disabled. Please use other means for backups
diff --git a/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php b/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php
index 1acd16708ff4..ea8a44a1122b 100644
--- a/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php
+++ b/app/code/Magento/Braintree/Controller/Paypal/PlaceOrder.php
@@ -9,6 +9,7 @@
use Magento\Braintree\Model\Paypal\Helper;
use Magento\Checkout\Model\Session;
use Magento\Framework\App\Action\Context;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Exception\LocalizedException;
@@ -17,7 +18,7 @@
/**
* Class PlaceOrder
*/
-class PlaceOrder extends AbstractAction
+class PlaceOrder extends AbstractAction implements HttpPostActionInterface
{
/**
* @var Helper\OrderPlace
@@ -54,6 +55,7 @@ public function __construct(
/**
* @inheritdoc
+ *
* @throws LocalizedException
*/
public function execute()
@@ -71,7 +73,10 @@ public function execute()
return $resultRedirect->setPath('checkout/onepage/success', ['_secure' => true]);
} catch (\Exception $e) {
$this->logger->critical($e);
- $this->messageManager->addExceptionMessage($e, $e->getMessage());
+ $this->messageManager->addExceptionMessage(
+ $e,
+ __('The order #%1 cannot be processed.', $quote->getReservedOrderId())
+ );
}
return $resultRedirect->setPath('checkout/cart', ['_secure' => true]);
diff --git a/app/code/Magento/Braintree/Controller/Paypal/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php
index ca252aabe54a..2923db6fa88c 100644
--- a/app/code/Magento/Braintree/Controller/Paypal/Review.php
+++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php
@@ -12,17 +12,25 @@
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;
+use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Payment\Model\Method\Logger;
/**
* Class Review
*/
-class Review extends AbstractAction
+class Review extends AbstractAction implements HttpPostActionInterface, HttpGetActionInterface
{
/**
* @var QuoteUpdater
*/
private $quoteUpdater;
+ /**
+ * @var Logger
+ */
+ private $logger;
+
/**
* @var string
*/
@@ -35,15 +43,18 @@ class Review extends AbstractAction
* @param Config $config
* @param Session $checkoutSession
* @param QuoteUpdater $quoteUpdater
+ * @param Logger $logger
*/
public function __construct(
Context $context,
Config $config,
Session $checkoutSession,
- QuoteUpdater $quoteUpdater
+ QuoteUpdater $quoteUpdater,
+ Logger $logger
) {
parent::__construct($context, $config, $checkoutSession);
$this->quoteUpdater = $quoteUpdater;
+ $this->logger = $logger;
}
/**
@@ -55,12 +66,13 @@ public function execute()
$this->getRequest()->getPostValue('result', '{}'),
true
);
+ $this->logger->debug($requestData);
$quote = $this->checkoutSession->getQuote();
try {
$this->validateQuote($quote);
- if ($this->validateRequestData($requestData)) {
+ if ($requestData && $this->validateRequestData($requestData)) {
$this->quoteUpdater->execute(
$requestData['nonce'],
$requestData['details'],
@@ -91,6 +103,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 a035c84b4caf..4d63ee4125b7 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 4280663178ef..950634ba2d9e 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/Multishipping/PlaceOrder.php b/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php
new file mode 100644
index 000000000000..a6c1b088400a
--- /dev/null
+++ b/app/code/Magento/Braintree/Model/Multishipping/PlaceOrder.php
@@ -0,0 +1,177 @@
+orderManagement = $orderManagement;
+ $this->paymentExtensionFactory = $paymentExtensionFactory;
+ $this->getPaymentNonceCommand = $getPaymentNonceCommand;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function place(array $orderList): array
+ {
+ if (empty($orderList)) {
+ return [];
+ }
+
+ $errorList = [];
+ $firstOrder = $this->orderManagement->place(array_shift($orderList));
+ // get payment token from first placed order
+ $paymentToken = $this->getPaymentToken($firstOrder);
+
+ foreach ($orderList as $order) {
+ try {
+ /** @var OrderInterface $order */
+ $orderPayment = $order->getPayment();
+ $this->setVaultPayment($orderPayment, $paymentToken);
+ $this->orderManagement->place($order);
+ } catch (\Exception $e) {
+ $incrementId = $order->getIncrementId();
+ $errorList[$incrementId] = $e;
+ }
+ }
+
+ return $errorList;
+ }
+
+ /**
+ * Sets vault payment method.
+ *
+ * @param OrderPaymentInterface $orderPayment
+ * @param PaymentTokenInterface $paymentToken
+ * @return void
+ */
+ private function setVaultPayment(OrderPaymentInterface $orderPayment, PaymentTokenInterface $paymentToken): void
+ {
+ $vaultMethod = $this->getVaultPaymentMethod(
+ $orderPayment->getMethod()
+ );
+ $orderPayment->setMethod($vaultMethod);
+
+ $publicHash = $paymentToken->getPublicHash();
+ $customerId = $paymentToken->getCustomerId();
+ $result = $this->getPaymentNonceCommand->execute(
+ ['public_hash' => $publicHash, 'customer_id' => $customerId]
+ )
+ ->get();
+
+ $orderPayment->setAdditionalInformation(
+ DataAssignObserver::PAYMENT_METHOD_NONCE,
+ $result['paymentMethodNonce']
+ );
+ $orderPayment->setAdditionalInformation(
+ PaymentTokenInterface::PUBLIC_HASH,
+ $publicHash
+ );
+ $orderPayment->setAdditionalInformation(
+ PaymentTokenInterface::CUSTOMER_ID,
+ $customerId
+ );
+ }
+
+ /**
+ * Returns vault payment method.
+ *
+ * For placing sequence of orders, we need to replace the original method on the vault method.
+ *
+ * @param string $method
+ * @return string
+ */
+ private function getVaultPaymentMethod(string $method): string
+ {
+ $vaultPaymentMap = [
+ ConfigProvider::CODE => ConfigProvider::CC_VAULT_CODE,
+ PaypalConfigProvider::PAYPAL_CODE => PaypalConfigProvider::PAYPAL_VAULT_CODE
+ ];
+
+ return $vaultPaymentMap[$method] ?? $method;
+ }
+
+ /**
+ * Returns payment token.
+ *
+ * @param OrderInterface $order
+ * @return PaymentTokenInterface
+ * @throws \BadMethodCallException
+ */
+ private function getPaymentToken(OrderInterface $order): PaymentTokenInterface
+ {
+ $orderPayment = $order->getPayment();
+ $extensionAttributes = $this->getExtensionAttributes($orderPayment);
+ $paymentToken = $extensionAttributes->getVaultPaymentToken();
+
+ if ($paymentToken === null) {
+ throw new \BadMethodCallException('Vault Payment Token should be defined for placed order payment.');
+ }
+
+ return $paymentToken;
+ }
+
+ /**
+ * Gets payment extension attributes.
+ *
+ * @param OrderPaymentInterface $payment
+ * @return OrderPaymentExtensionInterface
+ */
+ private function getExtensionAttributes(OrderPaymentInterface $payment): OrderPaymentExtensionInterface
+ {
+ $extensionAttributes = $payment->getExtensionAttributes();
+ if (null === $extensionAttributes) {
+ $extensionAttributes = $this->paymentExtensionFactory->create();
+ $payment->setExtensionAttributes($extensionAttributes);
+ }
+
+ return $extensionAttributes;
+ }
+}
diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php b/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php
index 6c4332ef22a4..314404c79939 100644
--- a/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php
+++ b/app/code/Magento/Braintree/Model/Paypal/Helper/OrderPlace.php
@@ -6,14 +6,15 @@
namespace Magento\Braintree\Model\Paypal\Helper;
-use Magento\Quote\Model\Quote;
+use Magento\Braintree\Model\Paypal\OrderCancellationService;
+use Magento\Checkout\Api\AgreementsValidatorInterface;
use Magento\Checkout\Helper\Data;
+use Magento\Checkout\Model\Type\Onepage;
use Magento\Customer\Model\Group;
use Magento\Customer\Model\Session;
-use Magento\Checkout\Model\Type\Onepage;
-use Magento\Quote\Api\CartManagementInterface;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Checkout\Api\AgreementsValidatorInterface;
+use Magento\Quote\Api\CartManagementInterface;
+use Magento\Quote\Model\Quote;
/**
* Class OrderPlace
@@ -42,23 +43,29 @@ class OrderPlace extends AbstractHelper
private $checkoutHelper;
/**
- * Constructor
- *
+ * @var OrderCancellationService
+ */
+ private $orderCancellationService;
+
+ /**
* @param CartManagementInterface $cartManagement
* @param AgreementsValidatorInterface $agreementsValidator
* @param Session $customerSession
* @param Data $checkoutHelper
+ * @param OrderCancellationService $orderCancellationService
*/
public function __construct(
CartManagementInterface $cartManagement,
AgreementsValidatorInterface $agreementsValidator,
Session $customerSession,
- Data $checkoutHelper
+ Data $checkoutHelper,
+ OrderCancellationService $orderCancellationService
) {
$this->cartManagement = $cartManagement;
$this->agreementsValidator = $agreementsValidator;
$this->customerSession = $customerSession;
$this->checkoutHelper = $checkoutHelper;
+ $this->orderCancellationService = $orderCancellationService;
}
/**
@@ -67,7 +74,7 @@ public function __construct(
* @param Quote $quote
* @param array $agreement
* @return void
- * @throws LocalizedException
+ * @throws \Exception
*/
public function execute(Quote $quote, array $agreement)
{
@@ -84,7 +91,12 @@ public function execute(Quote $quote, array $agreement)
$this->disabledQuoteAddressValidation($quote);
$quote->collectTotals();
- $this->cartManagement->placeOrder($quote->getId());
+ try {
+ $this->cartManagement->placeOrder($quote->getId());
+ } catch (\Exception $e) {
+ $this->orderCancellationService->execute($quote->getReservedOrderId());
+ throw $e;
+ }
}
/**
diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
index fe5895541543..ae2b1b142364 100644
--- a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
+++ b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
@@ -123,8 +123,8 @@ private function updateShippingAddress(Quote $quote, array $details)
{
$shippingAddress = $quote->getShippingAddress();
- $shippingAddress->setLastname($details['lastName']);
- $shippingAddress->setFirstname($details['firstName']);
+ $shippingAddress->setLastname($this->getShippingRecipientLastName($details));
+ $shippingAddress->setFirstname($this->getShippingRecipientFirstName($details));
$shippingAddress->setEmail($details['email']);
$shippingAddress->setCollectShippingRates(true);
@@ -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']);
@@ -188,4 +188,30 @@ private function updateAddressData(Address $address, array $addressData)
$address->setSameAsBilling(false);
$address->setCustomerAddressId(null);
}
+
+ /**
+ * Returns shipping recipient first name.
+ *
+ * @param array $details
+ * @return string
+ */
+ private function getShippingRecipientFirstName(array $details)
+ {
+ return isset($details['shippingAddress']['recipientName'])
+ ? explode(' ', $details['shippingAddress']['recipientName'], 2)[0]
+ : $details['firstName'];
+ }
+
+ /**
+ * Returns shipping recipient last name.
+ *
+ * @param array $details
+ * @return string
+ */
+ private function getShippingRecipientLastName(array $details)
+ {
+ return isset($details['shippingAddress']['recipientName'])
+ ? explode(' ', $details['shippingAddress']['recipientName'], 2)[1]
+ : $details['lastName'];
+ }
}
diff --git a/app/code/Magento/Braintree/Model/Paypal/OrderCancellationService.php b/app/code/Magento/Braintree/Model/Paypal/OrderCancellationService.php
new file mode 100644
index 000000000000..29757e35ea6f
--- /dev/null
+++ b/app/code/Magento/Braintree/Model/Paypal/OrderCancellationService.php
@@ -0,0 +1,77 @@
+searchCriteriaBuilder = $searchCriteriaBuilder;
+ $this->orderRepository = $orderRepository;
+ }
+
+ /**
+ * Cancels an order and authorization transaction.
+ *
+ * @param string $incrementId
+ * @return bool
+ */
+ public function execute(string $incrementId): bool
+ {
+ $order = $this->getOrder($incrementId);
+ if ($order === null) {
+ return false;
+ }
+
+ // `\Magento\Sales\Model\Service\OrderService::cancel` cannot be used for cancellation as the service uses
+ // the order repository with outdated payment method instance (ex. contains Vault instead of Braintree)
+ $order->cancel();
+ $this->orderRepository->save($order);
+ return true;
+ }
+
+ /**
+ * Gets order by increment ID.
+ *
+ * @param string $incrementId
+ * @return OrderInterface|null
+ */
+ private function getOrder(string $incrementId)
+ {
+ $searchCriteria = $this->searchCriteriaBuilder->addFilter(OrderInterface::INCREMENT_ID, $incrementId)
+ ->create();
+
+ $items = $this->orderRepository->getList($searchCriteria)
+ ->getItems();
+
+ return array_pop($items);
+ }
+}
diff --git a/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php b/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php
index e06b913db8ef..e6c5ee22c62b 100644
--- a/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php
+++ b/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php
@@ -47,6 +47,8 @@ public function __construct(Config $config, ResolverInterface $resolver)
*/
public function getConfig()
{
+ $requireBillingAddressAll = \Magento\Paypal\Model\Config::REQUIRE_BILLING_ADDRESS_ALL;
+
return [
'payment' => [
self::PAYPAL_CODE => [
@@ -60,6 +62,8 @@ public function getConfig()
'vaultCode' => self::PAYPAL_VAULT_CODE,
'skipOrderReview' => $this->config->isSkipOrderReview(),
'paymentIcon' => $this->config->getPayPalIcon(),
+ 'isRequiredBillingAddress' =>
+ (int)$this->config->isRequiredBillingAddress() === $requireBillingAddressAll
]
]
];
diff --git a/app/code/Magento/Braintree/Plugin/OrderCancellation.php b/app/code/Magento/Braintree/Plugin/OrderCancellation.php
new file mode 100644
index 000000000000..90c72839d977
--- /dev/null
+++ b/app/code/Magento/Braintree/Plugin/OrderCancellation.php
@@ -0,0 +1,81 @@
+orderCancellationService = $orderCancellationService;
+ $this->quoteRepository = $quoteRepository;
+ }
+
+ /**
+ * Cancels an order if an exception occurs during the order creation.
+ *
+ * @param CartManagementInterface $subject
+ * @param \Closure $proceed
+ * @param int $cartId
+ * @param PaymentInterface $payment
+ * @return int
+ * @throws \Exception
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function aroundPlaceOrder(
+ CartManagementInterface $subject,
+ \Closure $proceed,
+ $cartId,
+ PaymentInterface $payment = null
+ ) {
+ try {
+ return $proceed($cartId, $payment);
+ } catch (\Exception $e) {
+ $quote = $this->quoteRepository->get((int) $cartId);
+ $payment = $quote->getPayment();
+ $paymentCodes = [
+ ConfigProvider::CODE,
+ ConfigProvider::CC_VAULT_CODE,
+ PayPalConfigProvider::PAYPAL_CODE,
+ PayPalConfigProvider::PAYPAL_VAULT_CODE
+ ];
+ if (in_array($payment->getMethod(), $paymentCodes)) {
+ $incrementId = $quote->getReservedOrderId();
+ $this->orderCancellationService->execute($incrementId);
+ }
+
+ throw $e;
+ }
+ }
+}
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml
new file mode 100644
index 000000000000..ce1d0a9aecc9
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminOrderBraintreeFillActionGroup.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml
index e86d5403e11e..09ac0b77f861 100644
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminRoleActionGroup.xml
@@ -5,9 +5,9 @@
* See COPYING.txt for license details.
*/
-->
-
+
@@ -16,7 +16,7 @@
-
+
@@ -34,7 +34,6 @@
-
@@ -48,4 +47,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml
index 23c322083773..3f8bdaa4cd6b 100644
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/AdminUserActionGroup.xml
@@ -5,9 +5,9 @@
* See COPYING.txt for license details.
*/
-->
-
+
@@ -39,9 +39,8 @@
-
-
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml
index 27e2039fe526..cbb065704fbc 100644
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/ConfigureBraintreeActionGroup.xml
@@ -6,13 +6,13 @@
*/
-->
+ 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
deleted file mode 100644
index ee7158c2b63f..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewOrderActionGroup.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
deleted file mode 100644
index 65eddc0d9e51..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml
new file mode 100644
index 000000000000..bf06bc7df520
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Mftf/ActionGroup/StorefrontFillCartDataActionGroup.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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 8e55362a6cba..f00e3fa286b0 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Data/BraintreeData.xml
@@ -35,6 +35,34 @@
somePrivateKey
+
+ BraintreeTitle
+ PaymentAction
+ Environment
+ MerchantId
+ PublicKey
+ PrivateKey
+ Status
+
+
+ Credit Card (Braintree)
+
+
+ authorize
+
+
+ sandbox
+
+
+ d4pdjhxgjfrsmzbf
+
+
+ m7q4wmh43xrgyrst
+
+
+ 67de364080b1b4e2492d7a3de413a572
+
+
DefaultTitle
@@ -63,6 +91,28 @@
+
+ BraintreeValuteActive
+ EnableSolution
+
+
+ 1
+
+
+ 1
+
+
+
+ DefaultBraintreeValuteActive
+ DefaultEnableSolution
+
+
+ 0
+
+
+ 0
+
+
Website
new_website
@@ -79,7 +129,7 @@
John
Smith
admin123
- mail@mail.com
+ mail@mail.com
diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml
deleted file mode 100644
index 772c1c39a04c..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Data/NewCustomerData.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
- Abgar
- Abgaryan
- m@m.com
- Abgar
- Abgaryan
- Street
- Yerevan
- 9999
- 9999
-
-
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml b/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml
deleted file mode 100644
index 72661ae94076..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Data/NewProductData.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
- ProductTest
- 100
- 100
-
-
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml b/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml
index 83018852bfeb..5e3b870d65c6 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Metadata/braintree_config-meta.xml
@@ -42,4 +42,22 @@
+
+
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml
deleted file mode 100644
index 220c9a444b02..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteRoleSection.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml
deleted file mode 100644
index bf2e2b44eb60..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminDeleteUserSection.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml
index e37ce8f4738b..a34cdf15e7ad 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditRoleInfoSection.xml
@@ -5,7 +5,9 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml
index e999413c96d7..216292b81162 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserRoleSection.xml
@@ -5,7 +5,9 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml
index 2e5fcfb7b5c8..cee262864d8c 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminEditUserSection.xml
@@ -5,7 +5,9 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml
index 660c7393b406..24e5efdc610f 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminMenuSection.xml
@@ -7,13 +7,13 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
-
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml
index 63cbadc71d3d..1cf54bf94e77 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/AdminRoleGridSection.xml
@@ -5,7 +5,9 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml
deleted file mode 100644
index 9564bc61f799..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Section/AdminUserGridSection.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml
index 016af2e10274..f8802e9a34ae 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfiguraionSection.xml
@@ -5,7 +5,9 @@
* See COPYING.txt for license details.
*/
-->
-
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml
new file mode 100644
index 000000000000..d9f2b14a40e2
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/BraintreeConfigurationPaymentSection.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml
deleted file mode 100644
index 32f02a69f817..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Section/CatalogSubmenuSection.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml
deleted file mode 100644
index 100407438eaa..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationListSection.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml
index 885a45be721f..2192dd935c33 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/ConfigurationPaymentSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml
deleted file mode 100644
index e4a75b1b6a84..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersPageSection.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml
deleted file mode 100644
index 937afb83da96..000000000000
--- a/app/code/Magento/Braintree/Test/Mftf/Section/CustomersSubmenuSection.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml b/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml
index f094baa9f344..806762f82646 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml
+++ b/app/code/Magento/Braintree/Test/Mftf/Section/StoresSubmenuSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml
new file mode 100644
index 000000000000..a781841e0a77
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Mftf/Test/BraintreeCreditCardOnCheckoutTest.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CreateAnAdminOrderUsingBraintreePaymentTest1.xml
index df2e98816f0d..244052371e70 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 @@
-
+
+
+
+
-
-
+
@@ -41,47 +40,70 @@
-
+
+
-
+
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
-
-
-
+
-
+
-
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml
new file mode 100644
index 000000000000..cf51f29db79f
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Mftf/Test/CretateAdminOrderWithOnlinePaymentIncludingTaxAndDiscount.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php
index 4bea03153b93..9c25846e56da 100644
--- a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/PlaceOrderTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Braintree\Test\Unit\Controller\Paypal;
@@ -16,6 +17,8 @@
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Message\ManagerInterface;
use Magento\Quote\Model\Quote;
+use PHPUnit\Framework\MockObject\MockObject;
+use Psr\Log\LoggerInterface;
/**
* Class PlaceOrderTest
@@ -27,34 +30,34 @@
class PlaceOrderTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var OrderPlace|\PHPUnit_Framework_MockObject_MockObject
+ * @var OrderPlace|MockObject
*/
- private $orderPlaceMock;
+ private $orderPlace;
/**
- * @var Config|\PHPUnit_Framework_MockObject_MockObject
+ * @var Config|MockObject
*/
- private $configMock;
+ private $config;
/**
- * @var Session|\PHPUnit_Framework_MockObject_MockObject
+ * @var Session|MockObject
*/
- private $checkoutSessionMock;
+ private $checkoutSession;
/**
- * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var RequestInterface|MockObject
*/
- private $requestMock;
+ private $request;
/**
- * @var ResultFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var ResultFactory|MockObject
*/
- private $resultFactoryMock;
+ private $resultFactory;
/**
- * @var ManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var ManagerInterface|MockObject
*/
- protected $messageManagerMock;
+ private $messageManager;
/**
* @var PlaceOrder
@@ -62,139 +65,143 @@ class PlaceOrderTest extends \PHPUnit\Framework\TestCase
private $placeOrder;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
- private $loggerMock;
+ private $logger;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
- /** @var Context|\PHPUnit_Framework_MockObject_MockObject $contextMock */
- $contextMock = $this->getMockBuilder(Context::class)
+ /** @var Context|MockObject $context */
+ $context = $this->getMockBuilder(Context::class)
->disableOriginalConstructor()
->getMock();
- $this->requestMock = $this->getMockBuilder(RequestInterface::class)
+ $this->request = $this->getMockBuilder(RequestInterface::class)
->setMethods(['getPostValue'])
->getMockForAbstractClass();
- $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class)
+ $this->resultFactory = $this->getMockBuilder(ResultFactory::class)
->disableOriginalConstructor()
->getMock();
- $this->checkoutSessionMock = $this->getMockBuilder(Session::class)
+ $this->checkoutSession = $this->getMockBuilder(Session::class)
->disableOriginalConstructor()
->getMock();
- $this->configMock = $this->getMockBuilder(Config::class)
+ $this->config = $this->getMockBuilder(Config::class)
->disableOriginalConstructor()
->getMock();
- $this->orderPlaceMock = $this->getMockBuilder(OrderPlace::class)
+ $this->orderPlace = $this->getMockBuilder(OrderPlace::class)
->disableOriginalConstructor()
->getMock();
- $this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class)
+ $this->messageManager = $this->getMockBuilder(ManagerInterface::class)
->getMockForAbstractClass();
- $contextMock->expects(self::once())
- ->method('getRequest')
- ->willReturn($this->requestMock);
- $contextMock->expects(self::once())
- ->method('getResultFactory')
- ->willReturn($this->resultFactoryMock);
- $contextMock->expects(self::once())
- ->method('getMessageManager')
- ->willReturn($this->messageManagerMock);
-
- $this->loggerMock = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
+ $context->method('getRequest')
+ ->willReturn($this->request);
+ $context->method('getResultFactory')
+ ->willReturn($this->resultFactory);
+ $context->method('getMessageManager')
+ ->willReturn($this->messageManager);
+
+ $this->logger = $this->getMockBuilder(LoggerInterface::class)
->disableOriginalConstructor()
->getMock();
$this->placeOrder = new PlaceOrder(
- $contextMock,
- $this->configMock,
- $this->checkoutSessionMock,
- $this->orderPlaceMock,
- $this->loggerMock
+ $context,
+ $this->config,
+ $this->checkoutSession,
+ $this->orderPlace,
+ $this->logger
);
}
+ /**
+ * Checks if an order is placed successfully.
+ *
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NotFoundException
+ */
public function testExecute()
{
$agreement = ['test-data'];
$quoteMock = $this->getQuoteMock();
- $quoteMock->expects(self::once())
- ->method('getItemsCount')
+ $quoteMock->method('getItemsCount')
->willReturn(1);
$resultMock = $this->getResultMock();
- $resultMock->expects(self::once())
- ->method('setPath')
+ $resultMock->method('setPath')
->with('checkout/onepage/success')
->willReturnSelf();
- $this->resultFactoryMock->expects(self::once())
- ->method('create')
+ $this->resultFactory->method('create')
->with(ResultFactory::TYPE_REDIRECT)
->willReturn($resultMock);
- $this->requestMock->expects(self::once())
- ->method('getPostValue')
+ $this->request->method('getPostValue')
->with('agreement', [])
->willReturn($agreement);
- $this->checkoutSessionMock->expects(self::once())
- ->method('getQuote')
+ $this->checkoutSession->method('getQuote')
->willReturn($quoteMock);
- $this->orderPlaceMock->expects(self::once())
- ->method('execute')
+ $this->orderPlace->method('execute')
->with($quoteMock, [0]);
- $this->messageManagerMock->expects(self::never())
+ $this->messageManager->expects(self::never())
->method('addExceptionMessage');
self::assertEquals($this->placeOrder->execute(), $resultMock);
}
+ /**
+ * Checks a negative scenario during place order action.
+ *
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NotFoundException
+ */
public function testExecuteException()
{
$agreement = ['test-data'];
$quote = $this->getQuoteMock();
- $quote->expects(self::once())
- ->method('getItemsCount')
+ $quote->method('getItemsCount')
->willReturn(0);
+ $quote->method('getReservedOrderId')
+ ->willReturn('000000111');
$resultMock = $this->getResultMock();
- $resultMock->expects(self::once())
- ->method('setPath')
+ $resultMock->method('setPath')
->with('checkout/cart')
->willReturnSelf();
- $this->resultFactoryMock->expects(self::once())
- ->method('create')
+ $this->resultFactory->method('create')
->with(ResultFactory::TYPE_REDIRECT)
->willReturn($resultMock);
- $this->requestMock->expects(self::once())
- ->method('getPostValue')
+ $this->request->method('getPostValue')
->with('agreement', [])
->willReturn($agreement);
- $this->checkoutSessionMock->expects(self::once())
- ->method('getQuote')
+ $this->checkoutSession->method('getQuote')
->willReturn($quote);
- $this->orderPlaceMock->expects(self::never())
+ $this->orderPlace->expects(self::never())
->method('execute');
- $this->messageManagerMock->expects(self::once())
- ->method('addExceptionMessage')
+ $this->messageManager->method('addExceptionMessage')
->with(
self::isInstanceOf('\InvalidArgumentException'),
- 'Checkout failed to initialize. Verify and try again.'
+ 'The order #000000111 cannot be processed.'
);
self::assertEquals($this->placeOrder->execute(), $resultMock);
}
/**
- * @return ResultInterface|\PHPUnit_Framework_MockObject_MockObject
+ * Gets mock object for a result.
+ *
+ * @return ResultInterface|MockObject
*/
private function getResultMock()
{
@@ -204,7 +211,9 @@ private function getResultMock()
}
/**
- * @return Quote|\PHPUnit_Framework_MockObject_MockObject
+ * Gets mock object for a quote.
+ *
+ * @return Quote|MockObject
*/
private function getQuoteMock()
{
diff --git a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php
index 609b7f21dbf8..d68838bafbf0 100644
--- a/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Controller/Paypal/ReviewTest.php
@@ -6,6 +6,7 @@
namespace Magento\Braintree\Test\Unit\Controller\Paypal;
+use Magento\Payment\Model\Method\Logger;
use Magento\Quote\Model\Quote;
use Magento\Framework\View\Layout;
use Magento\Checkout\Model\Session;
@@ -65,6 +66,11 @@ class ReviewTest extends \PHPUnit\Framework\TestCase
*/
private $review;
+ /**
+ * @var Logger|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $loggerMock;
+
protected function setUp()
{
/** @var Context|\PHPUnit_Framework_MockObject_MockObject $contextMock */
@@ -88,6 +94,9 @@ protected function setUp()
->getMock();
$this->messageManagerMock = $this->getMockBuilder(ManagerInterface::class)
->getMockForAbstractClass();
+ $this->loggerMock = $this->getMockBuilder(Logger::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$contextMock->expects(self::once())
->method('getRequest')
@@ -103,7 +112,8 @@ protected function setUp()
$contextMock,
$this->configMock,
$this->checkoutSessionMock,
- $this->quoteUpdaterMock
+ $this->quoteUpdaterMock,
+ $this->loggerMock
);
}
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 25ccd8b32d10..d4e1f2745e3f 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/OrderPlaceTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/OrderPlaceTest.php
index 1aecba91b9af..c8524017274a 100644
--- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/OrderPlaceTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/OrderPlaceTest.php
@@ -3,9 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Braintree\Test\Unit\Model\Paypal\Helper;
use Magento\Braintree\Model\Paypal\Helper\OrderPlace;
+use Magento\Braintree\Model\Paypal\OrderCancellationService;
use Magento\Checkout\Api\AgreementsValidatorInterface;
use Magento\Checkout\Helper\Data;
use Magento\Checkout\Model\Type\Onepage;
@@ -14,6 +17,7 @@
use Magento\Quote\Api\CartManagementInterface;
use Magento\Quote\Model\Quote;
use Magento\Quote\Model\Quote\Address;
+use PHPUnit\Framework\MockObject\MockObject;
/**
* Class OrderPlaceTest
@@ -27,62 +31,80 @@ class OrderPlaceTest extends \PHPUnit\Framework\TestCase
const TEST_EMAIL = 'test@test.loc';
/**
- * @var CartManagementInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var CartManagementInterface|MockObject
*/
- private $cartManagementMock;
+ private $cartManagement;
/**
- * @var AgreementsValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var AgreementsValidatorInterface|MockObject
*/
- private $agreementsValidatorMock;
+ private $agreementsValidator;
/**
- * @var Session|\PHPUnit_Framework_MockObject_MockObject
+ * @var Session|MockObject
*/
- private $customerSessionMock;
+ private $customerSession;
/**
- * @var Data|\PHPUnit_Framework_MockObject_MockObject
+ * @var Data|MockObject
*/
- private $checkoutHelperMock;
+ private $checkoutHelper;
/**
- * @var Address|\PHPUnit_Framework_MockObject_MockObject
+ * @var Address|MockObject
*/
- private $billingAddressMock;
+ private $billingAddress;
/**
* @var OrderPlace
*/
private $orderPlace;
+ /**
+ * @var OrderCancellationService|MockObject
+ */
+ private $orderCancellation;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
- $this->cartManagementMock = $this->getMockBuilder(CartManagementInterface::class)
+ $this->cartManagement = $this->getMockBuilder(CartManagementInterface::class)
->getMockForAbstractClass();
- $this->agreementsValidatorMock = $this->getMockBuilder(AgreementsValidatorInterface::class)
+ $this->agreementsValidator = $this->getMockBuilder(AgreementsValidatorInterface::class)
->getMockForAbstractClass();
- $this->customerSessionMock = $this->getMockBuilder(Session::class)
+ $this->customerSession = $this->getMockBuilder(Session::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->checkoutHelper = $this->getMockBuilder(Data::class)
->disableOriginalConstructor()
->getMock();
- $this->checkoutHelperMock = $this->getMockBuilder(Data::class)
+
+ $this->orderCancellation = $this->getMockBuilder(OrderCancellationService::class)
->disableOriginalConstructor()
->getMock();
$this->orderPlace = new OrderPlace(
- $this->cartManagementMock,
- $this->agreementsValidatorMock,
- $this->customerSessionMock,
- $this->checkoutHelperMock
+ $this->cartManagement,
+ $this->agreementsValidator,
+ $this->customerSession,
+ $this->checkoutHelper,
+ $this->orderCancellation
);
}
+ /**
+ * Checks a scenario for a guest customer.
+ *
+ * @throws \Exception
+ */
public function testExecuteGuest()
{
$agreement = ['test', 'test'];
$quoteMock = $this->getQuoteMock();
- $this->agreementsValidatorMock->expects(self::once())
+ $this->agreementsValidator->expects(self::once())
->method('isValid')
->willReturn(true);
@@ -97,7 +119,7 @@ public function testExecuteGuest()
->method('getId')
->willReturn(10);
- $this->cartManagementMock->expects(self::once())
+ $this->cartManagement->expects(self::once())
->method('placeOrder')
->with(10);
@@ -105,9 +127,11 @@ public function testExecuteGuest()
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Disables address validation.
+ *
+ * @param MockObject $quoteMock
*/
- private function disabledQuoteAddressValidationStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock)
+ private function disabledQuoteAddressValidationStep(MockObject $quoteMock)
{
$billingAddressMock = $this->getBillingAddressMock($quoteMock);
$shippingAddressMock = $this->getMockBuilder(Address::class)
@@ -115,26 +139,21 @@ private function disabledQuoteAddressValidationStep(\PHPUnit_Framework_MockObjec
->disableOriginalConstructor()
->getMock();
- $quoteMock->expects(self::once())
- ->method('getShippingAddress')
+ $quoteMock->method('getShippingAddress')
->willReturn($shippingAddressMock);
- $billingAddressMock->expects(self::once())
- ->method('setShouldIgnoreValidation')
+ $billingAddressMock->method('setShouldIgnoreValidation')
->with(true)
->willReturnSelf();
- $quoteMock->expects(self::once())
- ->method('getIsVirtual')
+ $quoteMock->method('getIsVirtual')
->willReturn(false);
- $shippingAddressMock->expects(self::once())
- ->method('setShouldIgnoreValidation')
+ $shippingAddressMock->method('setShouldIgnoreValidation')
->with(true)
->willReturnSelf();
- $billingAddressMock->expects(self::any())
- ->method('getEmail')
+ $billingAddressMock->method('getEmail')
->willReturn(self::TEST_EMAIL);
$billingAddressMock->expects(self::never())
@@ -142,25 +161,24 @@ private function disabledQuoteAddressValidationStep(\PHPUnit_Framework_MockObjec
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Prepares checkout step.
+ *
+ * @param MockObject $quoteMock
*/
- private function getCheckoutMethodStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock)
+ private function getCheckoutMethodStep(MockObject $quoteMock)
{
- $this->customerSessionMock->expects(self::once())
- ->method('isLoggedIn')
+ $this->customerSession->method('isLoggedIn')
->willReturn(false);
$quoteMock->expects(self::at(1))
->method('getCheckoutMethod')
->willReturn(null);
- $this->checkoutHelperMock->expects(self::once())
- ->method('isAllowedGuestCheckout')
+ $this->checkoutHelper->method('isAllowedGuestCheckout')
->with($quoteMock)
->willReturn(true);
- $quoteMock->expects(self::once())
- ->method('setCheckoutMethod')
+ $quoteMock->method('setCheckoutMethod')
->with(Onepage::METHOD_GUEST);
$quoteMock->expects(self::at(2))
@@ -169,9 +187,11 @@ private function getCheckoutMethodStep(\PHPUnit_Framework_MockObject_MockObject
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Prepares quote.
+ *
+ * @param MockObject $quoteMock
*/
- private function prepareGuestQuoteStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock)
+ private function prepareGuestQuoteStep(MockObject $quoteMock)
{
$billingAddressMock = $this->getBillingAddressMock($quoteMock);
@@ -184,44 +204,44 @@ private function prepareGuestQuoteStep(\PHPUnit_Framework_MockObject_MockObject
->method('getEmail')
->willReturn(self::TEST_EMAIL);
- $quoteMock->expects(self::once())
- ->method('setCustomerEmail')
+ $quoteMock->method('setCustomerEmail')
->with(self::TEST_EMAIL)
->willReturnSelf();
- $quoteMock->expects(self::once())
- ->method('setCustomerIsGuest')
+ $quoteMock->method('setCustomerIsGuest')
->with(true)
->willReturnSelf();
- $quoteMock->expects(self::once())
- ->method('setCustomerGroupId')
+ $quoteMock->method('setCustomerGroupId')
->with(Group::NOT_LOGGED_IN_ID)
->willReturnSelf();
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
- * @return Address|\PHPUnit_Framework_MockObject_MockObject
+ * Gets a mock object for a billing address entity.
+ *
+ * @param MockObject $quoteMock
+ * @return Address|MockObject
*/
- private function getBillingAddressMock(\PHPUnit_Framework_MockObject_MockObject $quoteMock)
+ private function getBillingAddressMock(MockObject $quoteMock)
{
- if (!isset($this->billingAddressMock)) {
- $this->billingAddressMock = $this->getMockBuilder(Address::class)
+ if (!isset($this->billingAddress)) {
+ $this->billingAddress = $this->getMockBuilder(Address::class)
->setMethods(['setShouldIgnoreValidation', 'getEmail', 'setSameAsBilling'])
->disableOriginalConstructor()
->getMock();
}
- $quoteMock->expects(self::any())
- ->method('getBillingAddress')
- ->willReturn($this->billingAddressMock);
+ $quoteMock->method('getBillingAddress')
+ ->willReturn($this->billingAddress);
- return $this->billingAddressMock;
+ return $this->billingAddress;
}
/**
- * @return Quote|\PHPUnit_Framework_MockObject_MockObject
+ * Gets a mock object for a quote.
+ *
+ * @return Quote|MockObject
*/
private function getQuoteMock()
{
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php
index 62452228b618..c2678d1c7843 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',
@@ -163,7 +165,7 @@ private function getDetails()
'region' => 'IL',
'postalCode' => '60618',
'countryCodeAlpha2' => 'US',
- 'recipientName' => 'John Doe',
+ 'recipientName' => 'Jane Smith',
],
'billingAddress' => [
'streetAddress' => '123 Billing Street',
@@ -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')
- ->with($details['lastName']);
- $this->shippingAddressMock->expects(self::once())
- ->method('setFirstname')
- ->with($details['firstName']);
- $this->shippingAddressMock->expects(self::once())
- ->method('setEmail')
+ $this->shippingAddress->method('setLastname')
+ ->with('Smith');
+ $this->shippingAddress->method('setFirstname')
+ ->with('Jane');
+ $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/Test/Unit/Model/Report/BraintreeTransactionStub.php b/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php
index 372415d3530c..55e76cae9103 100644
--- a/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php
+++ b/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php
@@ -40,7 +40,7 @@ public function __get($name)
}
/**
- * Checks for the existance of a property stored in the private $_attributes property
+ * Checks for the existence of a property stored in the private $_attributes property
*
* @ignore
* @param string $name
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php
index 22f7f46bd98f..42469fe0faf4 100644
--- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php
@@ -77,6 +77,9 @@ public function testGetConfig($expected)
'width' => 30, 'height' => 26, 'url' => 'https://icon.test.url'
]);
+ $this->config->method('isRequiredBillingAddress')
+ ->willReturn(1);
+
self::assertEquals($expected, $this->configProvider->getConfig());
}
@@ -101,7 +104,8 @@ public function getConfigDataProvider()
'skipOrderReview' => false,
'paymentIcon' => [
'width' => 30, 'height' => 26, 'url' => 'https://icon.test.url'
- ]
+ ],
+ 'isRequiredBillingAddress' => true
]
]
]
diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json
index ba51a5436fed..2f956076f384 100644
--- a/app/code/Magento/Braintree/composer.json
+++ b/app/code/Magento/Braintree/composer.json
@@ -6,7 +6,7 @@
},
"require": {
"php": "~7.1.3||~7.2.0",
- "braintree/braintree_php": "3.34.0",
+ "braintree/braintree_php": "3.35.0",
"magento/framework": "*",
"magento/magento-composer-installer": "*",
"magento/module-catalog": "*",
@@ -21,7 +21,8 @@
"magento/module-quote": "*",
"magento/module-sales": "*",
"magento/module-ui": "*",
- "magento/module-vault": "*"
+ "magento/module-vault": "*",
+ "magento/module-multishipping": "*"
},
"suggest": {
"magento/module-checkout-agreements": "*",
diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml
index a830c2936875..fe4cfab9c0e3 100644
--- a/app/code/Magento/Braintree/etc/config.xml
+++ b/app/code/Magento/Braintree/etc/config.xml
@@ -42,6 +42,7 @@
cc_type,cc_number,avsPostalCodeResponseCode,avsStreetAddressResponseCode,cvvResponseCode,processorAuthorizationCode,processorResponseCode,processorResponseText,liabilityShifted,liabilityShiftPossible,riskDataId,riskDataDecision
Magento\Braintree\Model\AvsEmsCodeMapper
Magento\Braintree\Model\CvvEmsCodeMapper
+ braintree_group
BraintreePayPalFacade
@@ -67,6 +68,7 @@
processorResponseCode,processorResponseText,paymentId
processorResponseCode,processorResponseText,paymentId,payerEmail
en_US,en_GB,en_AU,da_DK,fr_FR,fr_CA,de_DE,zh_HK,it_IT,nl_NL,no_NO,pl_PL,es_ES,sv_SE,tr_TR,pt_BR,ja_JP,id_ID,ko_KR,pt_PT,ru_RU,th_TH,zh_CN,zh_TW
+ braintree_group
BraintreeCreditCardVaultFacade
@@ -76,6 +78,7 @@
Magento\Braintree\Model\InstantPurchase\CreditCard\TokenFormatter
Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider
+ braintree_group
BraintreePayPalVaultFacade
@@ -85,6 +88,7 @@
Magento\Braintree\Model\InstantPurchase\PayPal\TokenFormatter
Magento\Braintree\Model\InstantPurchase\PaymentAdditionalInformationProvider
+ braintree_group
diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml
index 67c90e6991e2..b81513caf17a 100644
--- a/app/code/Magento/Braintree/etc/di.xml
+++ b/app/code/Magento/Braintree/etc/di.xml
@@ -624,4 +624,8 @@
+
+
+
+
diff --git a/app/code/Magento/Braintree/etc/frontend/di.xml b/app/code/Magento/Braintree/etc/frontend/di.xml
index ea417c407dff..d8d3a93b71dc 100644
--- a/app/code/Magento/Braintree/etc/frontend/di.xml
+++ b/app/code/Magento/Braintree/etc/frontend/di.xml
@@ -61,4 +61,12 @@
Magento\Braintree\Model\LocaleResolver
+
+
+
+ - Magento\Braintree\Model\Multishipping\PlaceOrder
+ - Magento\Braintree\Model\Multishipping\PlaceOrder
+
+
+
diff --git a/app/code/Magento/Braintree/etc/payment.xml b/app/code/Magento/Braintree/etc/payment.xml
new file mode 100644
index 000000000000..4cae049aaf5a
--- /dev/null
+++ b/app/code/Magento/Braintree/etc/payment.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+
diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv
index 6bf677151ed0..e9145b35b56e 100644
--- a/app/code/Magento/Braintree/i18n/en_US.csv
+++ b/app/code/Magento/Braintree/i18n/en_US.csv
@@ -192,3 +192,5 @@ 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."
+"The order #%1 cannot be processed.","The order #%1 cannot be processed."
diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml
index 535a5a852fe7..4c15fffa8189 100644
--- a/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml
+++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/cc.phtml
@@ -83,7 +83,7 @@ $ccType = $block->getInfoData('cc_type');
id="= /* @noEscape */ $code ?>_vault"
name="payment[is_active_payment_token_enabler]"
class="admin__control-checkbox"/>
-
@@ -55,4 +56,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml
index e3ac6483bc7b..20bde5f87bd7 100644
--- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/EnableDisableProductActionGroup.xml
@@ -14,7 +14,8 @@
-
+
+
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 000000000000..441303e8f1b8
--- /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/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
index f28ffbdc40ac..1767db0a0097 100644
--- a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml
@@ -24,4 +24,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
new file mode 100644
index 000000000000..a00f1e367864
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml
new file mode 100644
index 000000000000..5cd286c0c6aa
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductsSummaryData.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ 1,968.00
+ 5.00
+ 1,973.00
+ Flat Rate - Fixed
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml
index e6866ae74a7e..256bfd774695 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Data/CustomAttributeData.xml
@@ -23,4 +23,12 @@
price_view
0
+
+ weight_type
+ 1
+
+
+ sku_type
+ 1
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
index af93200f946d..0a0c77755fc7 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml
@@ -31,6 +31,22 @@
$10.00
Default
+
+ FixedBundleProduct
+ fixed-bundle-product
+ bundle
+ 4
+ 1.23
+ 4
+ 1
+ fixed-bundle-product
+ CustomAttributeCategoryIds
+ EavStockItem
+ CustomAttributePriceView
+ CustomAttributeFixPrice
+ CustomAttributeFixWeight
+ CustomAttributeFixSku
+
Api Bundle Product
api-bundle-product
@@ -60,4 +76,19 @@
CustomAttributeDynamicPrice
CustomAttributePriceViewRange
+
+ Api Fixed Bundle Product
+ api-fixed-bundle-product
+ bundle
+ 4
+ 1.23
+ 4
+ 1
+ api-fixed-bundle-product
+ EavStockItem
+ ApiProductDescription
+ ApiProductShortDescription
+ CustomAttributeFixPrice
+ CustomAttributePriceView
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductDropdownOrderSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
new file mode 100644
index 000000000000..787f7ade8ffa
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml
index 814d03c52f4b..516f40ac2e7b 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductFormBundleSection.xml
@@ -22,6 +22,8 @@
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
index 8d9f29814f76..30a7e8b777f3 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontBundledSection.xml
@@ -9,6 +9,8 @@
+
+
@@ -24,10 +26,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index 735571375866..fae1ec331b66 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/AdminAddDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml
index 3c0034469769..c49202f31aef 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminAddDefaultVideoBundleProductTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml
index 3a70b189b4dc..c6a07f7ed95c 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminBasicBundleProductAttributesTest.xml
@@ -15,9 +15,6 @@
-
-
-
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml
new file mode 100644
index 000000000000..bc9a3dba9a5f
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleDynamicProductTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleFixedProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleFixedProductTest.xml
new file mode 100644
index 000000000000..2527dae7eadf
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminDeleteBundleFixedProductTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml
index c0edbf14e894..2f891fcc8f16 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminMassDeleteBundleProducts.xml
@@ -99,8 +99,8 @@
-
-
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml
index e3cb68b6664e..d050c5443d1f 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdminRemoveDefaultVideoBundleProductTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml
index 0b220efaad49..52bce6760088 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/AdvanceCatalogSearchBundleProductTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml
index 574c0dccdb07..c922b981aecd 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml
@@ -31,6 +31,10 @@
+
+
+
+
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 000000000000..ded8bb3c8333
--- /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 0fb8a7b88250..ff192538637e 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 @@
-
-
-
@@ -63,15 +60,7 @@
-
-
-
-
-
-
-
-
-
+
@@ -107,7 +96,8 @@
-
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml
new file mode 100644
index 000000000000..a1630128638d
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleOptionsToCartTest.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml
new file mode 100644
index 000000000000..33181d6e920e
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml
new file mode 100644
index 000000000000..4c39cbc4ab0a
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontCheckBundleProductOptionTierPrices.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Buy 5 for $5.00 each and save 50%
+ DropDownTierPriceTextProduct1
+
+
+
+
+
+
+ Buy 7 for $15.00 each and save 25%
+ dropDownTierPriceTextProduct2
+
+
+
+
+
+
+ Buy 5 for $5.00 each and save 50%
+ radioButtonsOptionTierPriceTextProduct1
+
+
+
+
+ Buy 7 for $15.00 each and save 25%
+ radioButtonsOptionTierPriceTextProduct2
+
+
+
+
+
+
+ Buy 5 for $5.00 each and save 50%
+ checkBoxOptionTierPriceTextProduct1
+
+
+
+
+ Buy 7 for $15.00 each and save 25%
+ checkBoxOptionTierPriceTextProduct2
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml
index f94cd83f4e7d..58806126aee3 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/Block/Catalog/Product/View/Type/BundleTest.php b/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php
index c252e5f99612..07d2e1b995cd 100644
--- a/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php
@@ -280,6 +280,7 @@ public function testGetJsonConfigFixedPriceBundle()
$this->assertEquals(110, $jsonConfig['prices']['oldPrice']['amount']);
$this->assertEquals(100, $jsonConfig['prices']['basePrice']['amount']);
$this->assertEquals(100, $jsonConfig['prices']['finalPrice']['amount']);
+ $this->assertEquals([1], $jsonConfig['positions']);
}
/**
diff --git a/app/code/Magento/Bundle/Test/Unit/Block/DataProviders/OptionPriceRendererTest.php b/app/code/Magento/Bundle/Test/Unit/Block/DataProviders/OptionPriceRendererTest.php
new file mode 100644
index 000000000000..1af73bafc625
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Unit/Block/DataProviders/OptionPriceRendererTest.php
@@ -0,0 +1,99 @@
+layoutMock = $this->createMock(
+ LayoutInterface::class
+ );
+
+ $this->renderer = $objectManager->getObject(
+ OptionPriceRenderer::class,
+ ['layout' => $this->layoutMock]
+ );
+ }
+
+ /**
+ * Test to render Tier price html
+ *
+ * @return void
+ */
+ public function testRenderTierPrice(): void
+ {
+ $expectedHtml = 'tier price html';
+ $expectedArguments = ['zone' => Render::ZONE_ITEM_OPTION];
+
+ $productMock = $this->createMock(Product::class);
+
+ $priceRenderer = $this->createPartialMock(BlockInterface::class, ['toHtml', 'render']);
+ $priceRenderer->expects($this->once())
+ ->method('render')
+ ->with('tier_price', $productMock, $expectedArguments)
+ ->willReturn($expectedHtml);
+
+ $this->layoutMock->method('getBlock')
+ ->with('product.price.render.default')
+ ->willReturn($priceRenderer);
+
+ $this->assertEquals(
+ $expectedHtml,
+ $this->renderer->renderTierPrice($productMock),
+ 'Render Tier price is wrong'
+ );
+ }
+
+ /**
+ * Test to render Tier price html when render block is not exists
+ *
+ * @return void
+ */
+ public function testRenderTierPriceNotExist(): void
+ {
+ $productMock = $this->createMock(Product::class);
+
+ $this->layoutMock->method('getBlock')
+ ->with('product.price.render.default')
+ ->willReturn(false);
+
+ $this->assertEquals(
+ '',
+ $this->renderer->renderTierPrice($productMock),
+ 'Render Tier price is wrong'
+ );
+ }
+}
diff --git a/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php b/app/code/Magento/Bundle/Test/Unit/Model/OptionRepositoryTest.php
index b4a466b413af..2450f63c3893 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/Plugin/Frontend/ProductTest.php b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/Frontend/ProductTest.php
new file mode 100644
index 000000000000..ee08618eab5d
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Plugin/Frontend/ProductTest.php
@@ -0,0 +1,73 @@
+product = $this->getMockBuilder(Product::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getEntityId'])
+ ->getMock();
+
+ $this->type = $this->getMockBuilder(Type::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getChildrenIds'])
+ ->getMock();
+
+ $this->plugin = new ProductPlugin($this->type);
+ }
+
+ public function testAfterGetIdentities()
+ {
+ $baseIdentities = [
+ 'SomeCacheId',
+ 'AnotherCacheId',
+ ];
+ $id = 12345;
+ $childIds = [
+ 1 => [1, 2, 5, 100500],
+ 12 => [7, 22, 45, 24612]
+ ];
+ $expectedIdentities = [
+ 'SomeCacheId',
+ 'AnotherCacheId',
+ Product::CACHE_TAG . '_' . 1,
+ Product::CACHE_TAG . '_' . 2,
+ Product::CACHE_TAG . '_' . 5,
+ Product::CACHE_TAG . '_' . 100500,
+ Product::CACHE_TAG . '_' . 7,
+ Product::CACHE_TAG . '_' . 22,
+ Product::CACHE_TAG . '_' . 45,
+ Product::CACHE_TAG . '_' . 24612,
+ ];
+ $this->product->expects($this->once())
+ ->method('getEntityId')
+ ->will($this->returnValue($id));
+ $this->type->expects($this->once())
+ ->method('getChildrenIds')
+ ->with($id)
+ ->will($this->returnValue($childIds));
+ $identities = $this->plugin->afterGetIdentities($this->product, $baseIdentities);
+ $this->assertEquals($expectedIdentities, $identities);
+ }
+}
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 4bbf5641c55d..9d7629c6f0a4 100644
--- a/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php
@@ -15,6 +15,7 @@
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Serialize\Serializer\Json;
+use Magento\Framework\Stdlib\ArrayUtils;
/**
* Class TypeTest
@@ -87,6 +88,11 @@ class TypeTest extends \PHPUnit\Framework\TestCase
*/
private $serializer;
+ /**
+ * @var ArrayUtils|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $arrayUtility;
+
/**
* @return void
*/
@@ -159,6 +165,11 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
+ $this->arrayUtility = $this->getMockBuilder(ArrayUtils::class)
+ ->setMethods(['flatten'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
$objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->model = $objectHelper->getObject(
\Magento\Bundle\Model\Product\Type::class,
@@ -175,6 +186,7 @@ protected function setUp()
'priceCurrency' => $this->priceCurrency,
'serializer' => $this->serializer,
'metadataPool' => $this->metadataPool,
+ 'arrayUtility' => $this->arrayUtility
]
);
}
@@ -421,6 +433,8 @@ function ($key) use ($optionCollection, $selectionCollection) {
return $resultValue;
}
);
+ $bundleOptions = [3 => 5];
+
$product->expects($this->any())
->method('getId')
->willReturn(333);
@@ -438,9 +452,7 @@ function ($key) use ($optionCollection, $selectionCollection) {
->with($selectionCollection, true, true);
$productType->expects($this->once())
->method('setStoreFilter');
- $buyRequest->expects($this->once())
- ->method('getBundleOption')
- ->willReturn([3 => 5]);
+ $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions);
$selectionCollection->expects($this->any())
->method('getItems')
->willReturn([$selection]);
@@ -491,6 +503,9 @@ function ($key) use ($optionCollection, $selectionCollection) {
$option->expects($this->once())
->method('getTitle')
->willReturn('Title for option');
+
+ $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions);
+
$buyRequest->expects($this->once())
->method('getBundleOptionQty')
->willReturn([3 => 5]);
@@ -513,10 +528,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);
}
@@ -657,6 +668,8 @@ function ($key) use ($optionCollection, $selectionCollection) {
return $resultValue;
}
);
+ $bundleOptions = [3 => 5];
+
$product->expects($this->any())
->method('getId')
->willReturn(333);
@@ -676,7 +689,10 @@ function ($key) use ($optionCollection, $selectionCollection) {
->method('setStoreFilter');
$buyRequest->expects($this->once())
->method('getBundleOption')
- ->willReturn([3 => 5]);
+ ->willReturn($bundleOptions);
+
+ $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions);
+
$selectionCollection->expects($this->any())
->method('getItems')
->willReturn([$selection]);
@@ -737,10 +753,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);
}
@@ -898,9 +910,10 @@ function ($key) use ($optionCollection, $selectionCollection) {
->with($selectionCollection, true, true);
$productType->expects($this->once())
->method('setStoreFilter');
- $buyRequest->expects($this->once())
- ->method('getBundleOption')
- ->willReturn([3 => 5]);
+
+ $bundleOptions = [3 => 5];
+ $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions);
+
$selectionCollection->expects($this->any())
->method('getItems')
->willReturn([$selection]);
@@ -951,6 +964,9 @@ function ($key) use ($optionCollection, $selectionCollection) {
$option->expects($this->once())
->method('getTitle')
->willReturn('Title for option');
+
+ $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions);
+
$buyRequest->expects($this->once())
->method('getBundleOptionQty')
->willReturn([3 => 5]);
@@ -961,10 +977,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);
}
@@ -1065,13 +1077,15 @@ function ($key) use ($optionCollection) {
->willReturn(333);
$productType->expects($this->once())
->method('setStoreFilter');
- $buyRequest->expects($this->once())
- ->method('getBundleOption')
- ->willReturn([]);
+
+ $bundleOptions = [];
+ $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions);
$buyRequest->expects($this->once())
->method('getBundleOptionQty')
->willReturn([3 => 5]);
+ $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions);
+
$result = $this->model->prepareForCartAdvanced($buyRequest, $product, 'single');
$this->assertEquals([$product], $result);
}
@@ -1177,9 +1191,12 @@ function ($key) use ($optionCollection, $selectionCollection) {
->with($selectionCollection, true, true);
$productType->expects($this->once())
->method('setStoreFilter');
- $buyRequest->expects($this->once())
- ->method('getBundleOption')
- ->willReturn([3 => 5]);
+
+ $bundleOptions = [3 => 5];
+ $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions);
+
+ $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions);
+
$selectionCollection->expects($this->at(0))
->method('getItems')
->willReturn([$selection]);
@@ -1301,9 +1318,12 @@ function ($key) use ($optionCollection, $selectionCollection) {
->willReturn($option);
$productType->expects($this->once())
->method('setStoreFilter');
- $buyRequest->expects($this->once())
- ->method('getBundleOption')
- ->willReturn([3 => 5]);
+
+ $bundleOptions = [3 => 5];
+ $buyRequest->expects($this->once())->method('getBundleOption')->willReturn($bundleOptions);
+
+ $this->arrayUtility->expects($this->once())->method('flatten')->willReturn($bundleOptions);
+
$selectionCollection->expects($this->any())
->method('getItems')
->willReturn([$selection]);
@@ -1595,7 +1615,7 @@ public function testGetSkuWithoutType()
->disableOriginalConstructor()
->getMock();
$selectionItemMock = $this->getMockBuilder(\Magento\Framework\DataObject::class)
- ->setMethods(['getSku', '__wakeup'])
+ ->setMethods(['getSku', 'getEntityId', '__wakeup'])
->disableOriginalConstructor()
->getMock();
@@ -1623,9 +1643,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/Test/Unit/Pricing/Price/SpecialPriceTest.php b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/SpecialPriceTest.php
index f38dfc5538cf..3e60e057fe62 100644
--- a/app/code/Magento/Bundle/Test/Unit/Pricing/Price/SpecialPriceTest.php
+++ b/app/code/Magento/Bundle/Test/Unit/Pricing/Price/SpecialPriceTest.php
@@ -6,6 +6,7 @@
namespace Magento\Bundle\Test\Unit\Pricing\Price;
use \Magento\Bundle\Pricing\Price\SpecialPrice;
+use Magento\Store\Api\Data\WebsiteInterface;
class SpecialPriceTest extends \PHPUnit\Framework\TestCase
{
@@ -77,12 +78,6 @@ public function testGetValue($regularPrice, $specialPrice, $isScopeDateInInterva
->method('getSpecialPrice')
->will($this->returnValue($specialPrice));
- $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->saleable->expects($this->once())
- ->method('getStore')
- ->will($this->returnValue($store));
$this->saleable->expects($this->once())
->method('getSpecialFromDate')
->will($this->returnValue($specialFromDate));
@@ -92,7 +87,7 @@ public function testGetValue($regularPrice, $specialPrice, $isScopeDateInInterva
$this->localeDate->expects($this->once())
->method('isScopeDateInInterval')
- ->with($store, $specialFromDate, $specialToDate)
+ ->with(WebsiteInterface::ADMIN_CODE, $specialFromDate, $specialToDate)
->will($this->returnValue($isScopeDateInInterval));
$this->priceCurrencyMock->expects($this->never())
diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
index 98fd96c52ccd..ad6fc12712c1 100644
--- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
+++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php
@@ -14,6 +14,7 @@
use Magento\Framework\UrlInterface;
use Magento\Ui\Component\Container;
use Magento\Ui\Component\Form;
+use Magento\Ui\Component\Form\Fieldset;
use Magento\Ui\Component\Modal;
/**
@@ -69,13 +70,26 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function modifyMeta(array $meta)
{
$meta = $this->removeFixedTierPrice($meta);
- $path = $this->arrayManager->findPath(static::CODE_BUNDLE_DATA, $meta, null, 'children');
+
+ $groupCode = static::CODE_BUNDLE_DATA;
+ $path = $this->arrayManager->findPath($groupCode, $meta, null, 'children');
+ if (empty($path)) {
+ $meta[$groupCode]['children'] = [];
+ $meta[$groupCode]['arguments']['data']['config'] = [
+ 'componentType' => Fieldset::NAME,
+ 'label' => __('Bundle Items'),
+ 'collapsible' => true
+ ];
+
+ $path = $this->arrayManager->findPath($groupCode, $meta, null, 'children');
+ }
$meta = $this->arrayManager->merge(
$path,
@@ -220,7 +234,7 @@ private function removeFixedTierPrice(array $meta)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyData(array $data)
{
diff --git a/app/code/Magento/Bundle/etc/db_schema.xml b/app/code/Magento/Bundle/etc/db_schema.xml
index 8092f34c533f..33738cd252d6 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,68 +101,68 @@
nullable="false" default="0" comment="Selection Price Value"/>
-
+
-
-
-
+
+ comment="Entity ID"/>
+ comment="Customer Group ID"/>
-
+
-
-
-
-
+
-
+
+ comment="Entity ID"/>
+ comment="Website ID"/>
+ comment="Stock ID"/>
-
+
@@ -172,13 +172,13 @@
+ comment="Entity ID"/>
+ comment="Website ID"/>
+ default="0" comment="Tax Class ID"/>
-
+
@@ -206,13 +206,13 @@
+ comment="Entity ID"/>
+ comment="Website ID"/>
+ default="0" comment="Tax Class ID"/>
-
+
@@ -240,11 +240,11 @@
+ comment="Entity ID"/>
+ comment="Website ID"/>
-
+
@@ -268,11 +268,11 @@
+ comment="Entity ID"/>
+ comment="Website ID"/>
-
+
@@ -296,11 +296,11 @@
+ comment="Entity ID"/>
+ comment="Website ID"/>
-
+
@@ -323,11 +323,11 @@
+ comment="Entity ID"/>
+ comment="Website ID"/>
-
+
diff --git a/app/code/Magento/Bundle/etc/di.xml b/app/code/Magento/Bundle/etc/di.xml
index 733b089dccd4..72155d922a25 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/Bundle/etc/frontend/di.xml b/app/code/Magento/Bundle/etc/frontend/di.xml
index 54f6d3b4b0f4..fc820ff87a12 100644
--- a/app/code/Magento/Bundle/etc/frontend/di.xml
+++ b/app/code/Magento/Bundle/etc/frontend/di.xml
@@ -13,4 +13,7 @@
+
+
+
diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml
index 224cd71538b7..a770ae864a74 100644
--- a/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml
+++ b/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml
@@ -20,9 +20,9 @@ $isElementReadonly = $block->getElement()
->getReadonly();
?>
-getCanReadPrice() === false)) { ?>
+getCanReadPrice() === false)): ?>
= /* @escapeNotVerified */ $elementHtml ?>
-
+
= $block->getExtendedElement($switchAttributeCode)->toHtml() ?>
@@ -43,13 +43,13 @@ $isElementReadonly = $block->getElement()
} else {
if ($attribute) {
getCanEditPrice() && $block->getCanReadPrice()
- && $block->getProduct()->isObjectNew()) { ?>
+ && $block->getProduct()->isObjectNew()): ?>
getDefaultProductPrice() ?: "''"; ?>
$attribute.value = = /* @escapeNotVerified */ $defaultProductPrice ?>;
-
+
$attribute.disabled = false;
$attribute.addClassName('required-entry');
-
+
}
if ($('dynamic-price-warning')) {
$('dynamic-price-warning').hide();
@@ -58,9 +58,9 @@ $isElementReadonly = $block->getElement()
}
getCanEditPrice()
- && !$block->getProduct()->isObjectNew())) { ?>
+ && !$block->getProduct()->isObjectNew())): ?>
$('= /* @escapeNotVerified */ $switchAttributeCode ?>').observe('change', = /* @escapeNotVerified */ $switchAttributeCode ?>_change);
-
+
Event.observe(window, 'load', function(){
= /* @escapeNotVerified */ $switchAttributeCode ?>_change();
});
diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml
index ff26d67bd837..12da960a9c6c 100644
--- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml
+++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml
@@ -28,8 +28,17 @@
+ getOrderItem()->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) ?
+ !$_item->getOrderItem()->isShipSeparately() : !$_item->getOrderItem()->getParentItem()->isShipSeparately()
+ ?>
setPriceDataObject($_item) ?>
getOrderItem()->getParentItem()): ?>
+
getSelectionAttributes($_item) ?>
@@ -60,14 +69,14 @@
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
= $block->getColumnHtml($_item, 'price') ?>
|
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
= /* @escapeNotVerified */ __('Ordered') ?> |
@@ -116,7 +125,7 @@
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
canEditQty()) : ?>
',
controlContainer: 'dd', // should be eliminated
priceFormat: {},
- isFixedPrice: false
+ isFixedPrice: false,
+ optionTierPricesBlocksSelector: '#option-tier-prices-{1} [data-role="selection-tier-prices"]'
};
$.widget('mage.priceBundle', {
@@ -91,6 +92,8 @@ define([
if (changes) {
priceBox.trigger('updatePrice', changes);
}
+
+ this._displayTierPriceBlock(bundleOption);
this.updateProductSummary();
},
@@ -207,6 +210,35 @@ define([
return this;
},
+ /**
+ * Show or hide option tier prices block
+ *
+ * @param {Object} optionElement
+ * @private
+ */
+ _displayTierPriceBlock: function (optionElement) {
+ var optionType = optionElement.prop('type'),
+ optionId,
+ optionValue,
+ optionTierPricesElements;
+
+ if (optionType === 'select-one') {
+ optionId = utils.findOptionId(optionElement[0]);
+ optionValue = optionElement.val() || null;
+ optionTierPricesElements = $(this.options.optionTierPricesBlocksSelector.replace('{1}', optionId));
+
+ _.each(optionTierPricesElements, function (tierPriceElement) {
+ var selectionId = $(tierPriceElement).data('selection-id') + '';
+
+ if (selectionId === optionValue) {
+ $(tierPriceElement).show();
+ } else {
+ $(tierPriceElement).hide();
+ }
+ });
+ }
+ },
+
/**
* Handler to update productSummary box
*/
@@ -374,8 +406,17 @@ define([
function applyTierPrice(oneItemPrice, qty, optionConfig) {
var tiers = optionConfig.tierPrice,
magicKey = _.keys(oneItemPrice)[0],
+ tiersFirstKey = _.keys(optionConfig)[0],
lowest = false;
+ if (!tiers) {//tiers is undefined when options has only one option
+ tiers = optionConfig[tiersFirstKey].tierPrice;
+ }
+
+ tiers.sort(function (a, b) {//sorting based on "price_qty"
+ return a['price_qty'] - b['price_qty'];
+ });
+
_.each(tiers, function (tier, index) {
if (tier['price_qty'] > qty) {
return;
diff --git a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml
index 5b8c050e5af5..d12f2e8f6a95 100644
--- a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml
+++ b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml
@@ -29,10 +29,22 @@
-
+
+
+ \Magento\Bundle\Block\DataProviders\OptionPriceRenderer
+
+
-
-
+
+
+ \Magento\Bundle\Block\DataProviders\OptionPriceRenderer
+
+
+
+
+ \Magento\Bundle\Block\DataProviders\OptionPriceRenderer
+
+
diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml
index bda649eb603e..830d03c826f3 100644
--- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml
+++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml
@@ -19,6 +19,7 @@
showSingle()): ?>
= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selections[0]) ?>
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?>
= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selection) ?>
+
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?>
diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml
index 7ea89e860981..1f33d97227ea 100644
--- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml
+++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml
@@ -21,6 +21,7 @@
showSingle()): ?>
= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?>
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?>
= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selection) ?>
+
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?>
diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml
index 977daa2b2a44..4ea00f62b204 100644
--- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml
+++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml
@@ -20,6 +20,7 @@
showSingle()): ?>
= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?>
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?>
+
+
+
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?>
+
+
+
diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml
index 063d66edb9e7..74e1c5f87495 100644
--- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml
+++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/items/renderer.phtml
@@ -7,95 +7,111 @@
// @codingStandardsIgnoreFile
/** @var $block \Magento\Bundle\Block\Sales\Order\Items\Renderer */
+$parentItem = $block->getItem();
+$items = array_merge([$parentItem], $parentItem->getChildrenItems());
+$index = 0;
+$prevOptionId = '';
?>
-getItem() ?>
-getChildrenItems()); ?>
-
-
+
-
-
- getItemOptions() || $parentItem->getDescription() || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem) && $parentItem->getGiftMessageId()): ?>
-
+ getItemOptions()
+ || $parentItem->getDescription()
+ || $this->helper('Magento\GiftMessage\Helper\Message')->isMessagesAllowed('order_item', $parentItem)
+ && $parentItem->getGiftMessageId()): ?>
+
-
+
- getParentItem()): ?>
- getSelectionAttributes($_item) ?>
-
+ getParentItem()): ?>
+ getSelectionAttributes($item) ?>
+
- = /* @escapeNotVerified */ $attributes['option_label'] ?> |
+ = $block->escapeHtml($attributes['option_label']); ?> |
-
+
- getParentItem()): ?> data-th="= /* @escapeNotVerified */ $attributes['option_label'] ?>">
- getParentItem()): ?>
-
- = $block->escapeHtml($_item->getName()) ?>
+ | getParentItem()): ?>
+ data-th="= $block->escapeHtml($attributes['option_label']); ?>"
+ >
+ getParentItem()): ?>
+
+ = $block->escapeHtml($item->getName()); ?>
|
- = $block->getValueHtml($_item) ?> |
+
+ = $block->getValueHtml($item); ?>
+ |
- = /* @escapeNotVerified */ $block->prepareSku($_item->getSku()) ?> |
-
- getParentItem()): ?>
- = $block->getItemPriceHtml() ?>
+ |
+ = /* @noEscape */ $block->prepareSku($item->getSku()); ?>
+ |
+
+ getParentItem()): ?>
+ = /* @noEscape */ $block->getItemPriceHtml(); ?>
|
-
+ |
getParentItem() && $block->isChildCalculated()) ||
- (!$_item->getParentItem() && !$block->isChildCalculated()) || ($_item->getQtyShipped() > 0 && $_item->getParentItem() && $block->isShipmentSeparately())):?>
+ ($item->getParentItem() && $block->isChildCalculated()) ||
+ (!$item->getParentItem() && !$block->isChildCalculated()) ||
+ ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately())): ?>
- getParentItem() && $block->isChildCalculated()) ||
- (!$_item->getParentItem() && !$block->isChildCalculated())): ?>
- getQtyOrdered() > 0): ?>
+ getParentItem() && $block->isChildCalculated()) ||
+ (!$item->getParentItem() && !$block->isChildCalculated())): ?>
+ getQtyOrdered() > 0): ?>
-
- = /* @escapeNotVerified */ __('Ordered') ?>
- = /* @escapeNotVerified */ $_item->getQtyOrdered()*1 ?>
+ = $block->escapeHtml(__('Ordered')); ?>
+ = /* @noEscape */ $item->getQtyOrdered() * 1; ?>
- getQtyShipped() > 0 && !$block->isShipmentSeparately()): ?>
+ getQtyShipped() > 0 && !$block->isShipmentSeparately()): ?>
-
- = /* @escapeNotVerified */ __('Shipped') ?>
- = /* @escapeNotVerified */ $_item->getQtyShipped()*1 ?>
+ = $block->escapeHtml(__('Shipped')); ?>
+ = /* @noEscape */ $item->getQtyShipped() * 1; ?>
- getQtyCanceled() > 0): ?>
+ getQtyCanceled() > 0): ?>
-
- = /* @escapeNotVerified */ __('Canceled') ?>
- = /* @escapeNotVerified */ $_item->getQtyCanceled()*1 ?>
+ = $block->escapeHtml(__('Canceled')); ?>
+ = /* @noEscape */ $item->getQtyCanceled() * 1; ?>
- getQtyRefunded() > 0): ?>
+ getQtyRefunded() > 0): ?>
-
- = /* @escapeNotVerified */ __('Refunded') ?>
- = /* @escapeNotVerified */ $_item->getQtyRefunded()*1 ?>
+ = $block->escapeHtml(__('Refunded')); ?>
+ = /* @noEscape */ $item->getQtyRefunded() * 1; ?>
- getQtyShipped() > 0 && $_item->getParentItem() && $block->isShipmentSeparately()): ?>
+ getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately()): ?>
-
- = /* @escapeNotVerified */ __('Shipped') ?>
- = /* @escapeNotVerified */ $_item->getQtyShipped()*1 ?>
+ = $block->escapeHtml(__('Shipped')); ?>
+ = /* @noEscape */ $item->getQtyShipped() * 1; ?>
-
+ = /* @noEscape */ $parentItem->getQtyOrdered() * 1; ?>
getParentItem() && $block->isChildCalculated()) ||
- (!$_item->getParentItem() && !$block->isChildCalculated()) || ($_item->getQtyShipped() > 0 && $_item->getParentItem() && $block->isShipmentSeparately())):?>
+ ($item->getParentItem() && $block->isChildCalculated()) ||
+ (!$item->getParentItem() && !$block->isChildCalculated()) ||
+ ($item->getQtyShipped() > 0 && $item->getParentItem() && $block->isShipmentSeparately())):?>
|
- getParentItem()): ?>
- = $block->getItemRowTotalHtml() ?>
+ getParentItem()): ?>
+ = /* @noEscape */ $block->getItemRowTotalHtml(); ?>
@@ -103,33 +119,38 @@
|
-getItemOptions()) || $block->escapeHtml($_item->getDescription()))): ?>
+getItemOptions()) || $block->escapeHtml($item->getDescription()))): ?>
- getItemOptions()): ?>
+ getItemOptions()): ?>
-
- - = $block->escapeHtml($_option['label']) ?>
+
+ - = $block->escapeHtml($option['label']) ?>
getPrintStatus()): ?>
- getFormatedOptionValue($_option) ?>
- - class="tooltip wrapper">
- = /* @escapeNotVerified */ $_formatedOptionValue['value'] ?>
-
+ getFormatedOptionValue($option) ?>
+
-
+ class="tooltip wrapper"
+ >
+ = /* @noEscape */ $formattedOptionValue['value'] ?>
+
- - = $block->escapeHtml($_option['label']) ?>
- - = /* @escapeNotVerified */ $_formatedOptionValue['full_view'] ?>
+ - = $block->escapeHtml($option['label']); ?>
+ - = /* @noEscape */ $formattedOptionValue['full_view']; ?>
- - = $block->escapeHtml((isset($_option['print_value']) ? $_option['print_value'] : $_option['value'])) ?>
+ - = $block->escapeHtml((isset($option['print_value']) ?
+ $option['print_value'] :
+ $option['value'])); ?>
+
- = $block->escapeHtml($_item->getDescription()) ?>
+ = $block->escapeHtml($item->getDescription()); ?>
|
diff --git a/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js b/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js
index d8d4cb1e99b7..1e7fe6b6673d 100644
--- a/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js
+++ b/app/code/Magento/Bundle/view/frontend/web/js/product-summary.js
@@ -56,8 +56,9 @@ define([
// Clear Summary box
this.element.html('');
-
- $.each(this.cache.currentElement.selected, $.proxy(this._renderOption, this));
+ this.cache.currentElement.positions.forEach(function (optionId) {
+ this._renderOption(optionId, this.cache.currentElement.selected[optionId]);
+ }, this);
this.element
.parents(this.options.bundleSummaryContainer)
.toggleClass('empty', !this.cache.currentElementCount); // Zero elements equal '.empty' container
diff --git a/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php b/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php
index b904d3f62a74..211d625fbc75 100644
--- a/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php
+++ b/app/code/Magento/BundleGraphQl/Model/BundleProductTypeResolver.php
@@ -8,19 +8,22 @@
namespace Magento\BundleGraphQl\Model;
use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface;
+use Magento\Bundle\Model\Product\Type as Type;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class BundleProductTypeResolver implements TypeResolverInterface
{
+ const BUNDLE_PRODUCT = 'BundleProduct';
+
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function resolveType(array $data) : string
{
- if (isset($data['type_id']) && $data['type_id'] == 'bundle') {
- return 'BundleProduct';
+ if (isset($data['type_id']) && $data['type_id'] == Type::TYPE_CODE) {
+ return self::BUNDLE_PRODUCT;
}
return '';
}
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItemLinks.php b/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItemLinks.php
index f90945d19f94..184f7177a995 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItemLinks.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItemLinks.php
@@ -7,15 +7,15 @@
namespace Magento\BundleGraphQl\Model\Resolver;
+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;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class BundleItemLinks implements ResolverInterface
{
@@ -42,16 +42,14 @@ public function __construct(
}
/**
- * {@inheritDoc}
+ * @inheritdoc
*/
- public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) : Value
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
if (!isset($value['option_id']) || !isset($value['parent_id'])) {
- $result = function () {
- return null;
- };
- return $this->valueFactory->create($result);
+ throw new LocalizedException(__('"option_id" and "parent_id" values should be specified'));
}
+
$this->linkCollection->addIdFilters((int)$value['option_id'], (int)$value['parent_id']);
$result = function () use ($value) {
return $this->linkCollection->getLinksForOptionId((int)$value['option_id']);
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItems.php b/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItems.php
index 9474f825fe5e..b67bd69ecf92 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItems.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/BundleItems.php
@@ -13,12 +13,11 @@
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\GraphQl\Config\Element\Field;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class BundleItems implements ResolverInterface
{
@@ -35,21 +34,21 @@ class BundleItems implements ResolverInterface
/**
* @var MetadataPool
*/
- private $metdataPool;
+ private $metadataPool;
/**
* @param Collection $bundleOptionCollection
* @param ValueFactory $valueFactory
- * @param MetadataPool $metdataPool
+ * @param MetadataPool $metadataPool
*/
public function __construct(
Collection $bundleOptionCollection,
ValueFactory $valueFactory,
- MetadataPool $metdataPool
+ MetadataPool $metadataPool
) {
$this->bundleOptionCollection = $bundleOptionCollection;
$this->valueFactory = $valueFactory;
- $this->metdataPool = $metdataPool;
+ $this->metadataPool = $metadataPool;
}
/**
@@ -57,9 +56,9 @@ public function __construct(
*
* {@inheritDoc}
*/
- public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null) : Value
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
- $linkField = $this->metdataPool->getMetadata(ProductInterface::class)->getLinkField();
+ $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
if ($value['type_id'] !== Type::TYPE_CODE
|| !isset($value[$linkField])
|| !isset($value[ProductInterface::SKU])
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php
index 149155c86275..7608d6e9e4d9 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php
@@ -61,6 +61,7 @@ public function __construct(
* Add parent id/sku pair to use for option filter at fetch time.
*
* @param int $parentId
+ * @param int $parentEntityId
* @param string $sku
*/
public function addParentFilterData(int $parentId, int $parentEntityId, string $sku) : void
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
index a4757108ee5a..de72b18982c1 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Options/Label.php
@@ -7,10 +7,10 @@
namespace Magento\BundleGraphQl\Model\Resolver\Options;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\Product;
+use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Deferred\Product as ProductDataProvider;
use Magento\Framework\GraphQl\Config\Element\Field;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -19,29 +19,28 @@
*/
class Label implements ResolverInterface
{
-
/**
* @var ValueFactory
*/
private $valueFactory;
/**
- * @var Product
+ * @var ProductDataProvider
*/
private $product;
/**
* @param ValueFactory $valueFactory
- * @param Product $product
+ * @param ProductDataProvider $product
*/
- public function __construct(ValueFactory $valueFactory, Product $product)
+ public function __construct(ValueFactory $valueFactory, ProductDataProvider $product)
{
$this->valueFactory = $valueFactory;
$this->product = $product;
}
/**
- * @inheritDoc
+ * @inheritdoc
*/
public function resolve(
Field $field,
@@ -49,12 +48,9 @@ public function resolve(
ResolveInfo $info,
array $value = null,
array $args = null
- ): Value {
+ ) {
if (!isset($value['sku'])) {
- $result = function () {
- return null;
- };
- return $this->valueFactory->create($result);
+ throw new LocalizedException(__('"sku" value should be specified'));
}
$this->product->addProductSku($value['sku']);
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicPrice.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicPrice.php
index e8dc3decc2ad..978e1c455fc0 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicPrice.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicPrice.php
@@ -5,36 +5,20 @@
*/
declare(strict_types=1);
-
namespace Magento\BundleGraphQl\Model\Resolver\Product\Fields;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Bundle\Model\Product\Type as Bundle;
use Magento\Framework\GraphQl\Config\Element\Field;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
-use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class DynamicPrice implements ResolverInterface
{
/**
- * @var ValueFactory
- */
- private $valueFactory;
-
- /**
- * @param ValueFactory $valueFactory
- */
- public function __construct(ValueFactory $valueFactory)
- {
- $this->valueFactory = $valueFactory;
- }
-
- /**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function resolve(
Field $field,
@@ -42,16 +26,12 @@ public function resolve(
ResolveInfo $info,
array $value = null,
array $args = null
- ): Value {
+ ) {
$result = null;
if ($value['type_id'] === Bundle::TYPE_CODE) {
$result = isset($value['price_type']) ? !$value['price_type'] : null;
}
- return $this->valueFactory->create(
- function () use ($result) {
- return $result;
- }
- );
+ return $result;
}
}
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicSku.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicSku.php
index 37e1557d36df..73f84c278a63 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicSku.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicSku.php
@@ -5,36 +5,20 @@
*/
declare(strict_types=1);
-
namespace Magento\BundleGraphQl\Model\Resolver\Product\Fields;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Bundle\Model\Product\Type as Bundle;
use Magento\Framework\GraphQl\Config\Element\Field;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
-use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class DynamicSku implements ResolverInterface
{
/**
- * @var ValueFactory
- */
- private $valueFactory;
-
- /**
- * @param ValueFactory $valueFactory
- */
- public function __construct(ValueFactory $valueFactory)
- {
- $this->valueFactory = $valueFactory;
- }
-
- /**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function resolve(
Field $field,
@@ -42,18 +26,12 @@ public function resolve(
ResolveInfo $info,
array $value = null,
array $args = null
- ): Value {
- $result = function () {
- return null;
- };
+ ) {
+ $result = null;
if ($value['type_id'] === Bundle::TYPE_CODE) {
$result = isset($value['sku_type']) ? !$value['sku_type'] : null;
}
- return $this->valueFactory->create(
- function () use ($result) {
- return $result;
- }
- );
+ return $result;
}
}
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicWeight.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicWeight.php
index 5f79bba449e5..a4bb8ef64fc9 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicWeight.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/DynamicWeight.php
@@ -5,36 +5,20 @@
*/
declare(strict_types=1);
-
namespace Magento\BundleGraphQl\Model\Resolver\Product\Fields;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Bundle\Model\Product\Type as Bundle;
use Magento\Framework\GraphQl\Config\Element\Field;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
-use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class DynamicWeight implements ResolverInterface
{
/**
- * @var ValueFactory
- */
- private $valueFactory;
-
- /**
- * @param ValueFactory $valueFactory
- */
- public function __construct(ValueFactory $valueFactory)
- {
- $this->valueFactory = $valueFactory;
- }
-
- /**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function resolve(
Field $field,
@@ -42,18 +26,12 @@ public function resolve(
ResolveInfo $info,
array $value = null,
array $args = null
- ): Value {
- $result = function () {
- return null;
- };
+ ) {
+ $result = null;
if ($value['type_id'] === Bundle::TYPE_CODE) {
$result = isset($value['weight_type']) ? !$value['weight_type'] : null;
}
- return $this->valueFactory->create(
- function () use ($result) {
- return $result;
- }
- );
+ return $result;
}
}
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/PriceView.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/PriceView.php
index ef8e93748c73..b7351b09d437 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/PriceView.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/PriceView.php
@@ -5,19 +5,16 @@
*/
declare(strict_types=1);
-
namespace Magento\BundleGraphQl\Model\Resolver\Product\Fields;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Bundle\Model\Product\Type as Bundle;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\EnumLookup;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
-use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class PriceView implements ResolverInterface
{
@@ -26,23 +23,16 @@ class PriceView implements ResolverInterface
*/
private $enumLookup;
- /**
- * @var ValueFactory
- */
- private $valueFactory;
-
/**
* @param EnumLookup $enumLookup
- * @param ValueFactory $valueFactory
*/
- public function __construct(EnumLookup $enumLookup, ValueFactory $valueFactory)
+ public function __construct(EnumLookup $enumLookup)
{
$this->enumLookup = $enumLookup;
- $this->valueFactory = $valueFactory;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function resolve(
Field $field,
@@ -50,19 +40,13 @@ public function resolve(
ResolveInfo $info,
array $value = null,
array $args = null
- ): Value {
- $result = function () {
- return null;
- };
+ ) {
+ $result = null;
if ($value['type_id'] === Bundle::TYPE_CODE) {
$result = isset($value['price_view'])
? $this->enumLookup->getEnumValueFromField('PriceViewEnum', $value['price_view']) : null;
}
- return $this->valueFactory->create(
- function () use ($result) {
- return $result;
- }
- );
+ return $result;
}
}
diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/ShipBundleItems.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/ShipBundleItems.php
index e2bd12a84e2b..6babf6520e10 100644
--- a/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/ShipBundleItems.php
+++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Product/Fields/ShipBundleItems.php
@@ -5,19 +5,16 @@
*/
declare(strict_types=1);
-
namespace Magento\BundleGraphQl\Model\Resolver\Product\Fields;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Bundle\Model\Product\Type as Bundle;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\EnumLookup;
-use Magento\Framework\GraphQl\Query\Resolver\Value;
-use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class ShipBundleItems implements ResolverInterface
{
@@ -26,23 +23,16 @@ class ShipBundleItems implements ResolverInterface
*/
private $enumLookup;
- /**
- * @var ValueFactory
- */
- private $valueFactory;
-
/**
* @param EnumLookup $enumLookup
- * @param ValueFactory $valueFactory
*/
- public function __construct(EnumLookup $enumLookup, ValueFactory $valueFactory)
+ public function __construct(EnumLookup $enumLookup)
{
$this->enumLookup = $enumLookup;
- $this->valueFactory = $valueFactory;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function resolve(
Field $field,
@@ -50,19 +40,10 @@ public function resolve(
ResolveInfo $info,
array $value = null,
array $args = null
- ): Value {
- $result = function () {
- return null;
- };
- if ($value['type_id'] === Bundle::TYPE_CODE) {
- $result = isset($value['shipment_type'])
- ? $this->enumLookup->getEnumValueFromField('ShipBundleItemsEnum', $value['shipment_type']) : null;
- }
+ ) {
+ $result = isset($value['shipment_type']) && $value['type_id'] === Bundle::TYPE_CODE
+ ? $this->enumLookup->getEnumValueFromField('ShipBundleItemsEnum', $value['shipment_type']) : null;
- return $this->valueFactory->create(
- function () use ($result) {
- return $result;
- }
- );
+ return $result;
}
}
diff --git a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php
index e0c94097e4d3..2cefc60a4297 100644
--- a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php
+++ b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php
@@ -322,7 +322,7 @@ function ($title, $storeName) {
*/
protected function getTypeValue($type)
{
- return isset($this->typeMapping[$type]) ? $this->typeMapping[$type] : self::VALUE_DYNAMIC;
+ return $this->typeMapping[$type] ?? self::VALUE_DYNAMIC;
}
/**
@@ -333,7 +333,7 @@ protected function getTypeValue($type)
*/
protected function getPriceViewValue($type)
{
- return isset($this->priceViewMapping[$type]) ? $this->priceViewMapping[$type] : self::VALUE_PRICE_RANGE;
+ return $this->priceViewMapping[$type] ?? self::VALUE_PRICE_RANGE;
}
/**
@@ -344,7 +344,7 @@ protected function getPriceViewValue($type)
*/
protected function getPriceTypeValue($type)
{
- return isset($this->priceTypeMapping[$type]) ? $this->priceTypeMapping[$type] : null;
+ return $this->priceTypeMapping[$type] ?? null;
}
/**
@@ -355,7 +355,7 @@ protected function getPriceTypeValue($type)
*/
private function getShipmentTypeValue($type)
{
- return isset($this->shipmentTypeMapping[$type]) ? $this->shipmentTypeMapping[$type] : null;
+ return $this->shipmentTypeMapping[$type] ?? null;
}
/**
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 3ed7e144ddd5..81a47d72602b 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/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php
index b0794f456464..a8650a4e6e9e 100644
--- a/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php
+++ b/app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php
@@ -242,7 +242,7 @@ public function testSaveData($skus, $bunch, $allowImport)
'price_type' => 'fixed',
'shipment_type' => '1',
'default_qty' => '1',
- 'is_defaul' => '1',
+ 'is_default' => '1',
'position' => '1',
'option_id' => '1']
]
@@ -264,7 +264,7 @@ public function testSaveData($skus, $bunch, $allowImport)
'price_type' => 'percent',
'shipment_type' => 0,
'default_qty' => '2',
- 'is_defaul' => '1',
+ 'is_default' => '1',
'position' => '6',
'option_id' => '6']
]
@@ -324,7 +324,7 @@ public function saveDataProvider()
. 'price_type=fixed,'
. 'shipment_type=separately,'
. 'default_qty=1,'
- . 'is_defaul=1,'
+ . 'is_default=1,'
. 'position=1,'
. 'option_id=1 | name=Bundle2,'
. 'type=dropdown,'
@@ -333,7 +333,7 @@ public function saveDataProvider()
. 'price=10,'
. 'price_type=fixed,'
. 'default_qty=1,'
- . 'is_defaul=1,'
+ . 'is_default=1,'
. 'position=2,'
. 'option_id=2'
],
diff --git a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
index 8acf170d43cf..b2aa0d000e9c 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,66 @@ 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('|', $formattedTagsChunk);
+ $formattedTagsChunk = [];
+ $tagsBatchSize = 0;
+ }
+
+ $tagsBatchSize += strlen($formattedTag);
+ $formattedTagsChunk[] = $formattedTag;
+ }
+ if (!empty($formattedTagsChunk)) {
+ yield implode('|', $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];
+ $unresponsiveServerError = [];
foreach ($servers as $server) {
$headers['Host'] = $server->getHost();
try {
@@ -69,12 +132,31 @@ public function sendPurgeRequest($tagsPattern)
$socketAdapter->read();
$socketAdapter->close();
} catch (\Exception $e) {
- $this->logger->critical($e->getMessage(), compact('server', 'tagsPattern'));
+ $unresponsiveServerError[] = "Cache host: " . $server->getHost() . ":" . $server->getPort() .
+ "resulted in error message: " . $e->getMessage();
+ }
+ }
+
+ $errorCount = count($unresponsiveServerError);
+
+ if ($errorCount > 0) {
+ $loggerMessage = implode(" ", $unresponsiveServerError);
+
+ if ($errorCount == count($servers)) {
+ $this->logger->critical(
+ 'No cache server(s) could be purged ' . $loggerMessage,
+ compact('server', 'formattedTagsChunk')
+ );
return false;
}
+
+ $this->logger->warning(
+ 'Unresponsive cache server(s) hit' . $loggerMessage,
+ compact('server', 'formattedTagsChunk')
+ );
}
- $this->logger->execute(compact('servers', 'tagsPattern'));
+ $this->logger->execute(compact('servers', 'formattedTagsChunk'));
return true;
}
}
diff --git a/app/code/Magento/Captcha/Controller/Refresh/Index.php b/app/code/Magento/Captcha/Controller/Refresh/Index.php
index e89a80646ed8..e401e03e9551 100644
--- a/app/code/Magento/Captcha/Controller/Refresh/Index.php
+++ b/app/code/Magento/Captcha/Controller/Refresh/Index.php
@@ -8,9 +8,10 @@
*/
namespace Magento\Captcha\Controller\Refresh;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\App\Action\Context;
-class Index extends \Magento\Framework\App\Action\Action
+class Index extends \Magento\Framework\App\Action\Action implements HttpPostActionInterface
{
/**
* @var \Magento\Captcha\Helper\Data
diff --git a/app/code/Magento/Captcha/CustomerData/Captcha.php b/app/code/Magento/Captcha/CustomerData/Captcha.php
new file mode 100644
index 000000000000..a744daacdc67
--- /dev/null
+++ b/app/code/Magento/Captcha/CustomerData/Captcha.php
@@ -0,0 +1,61 @@
+helper = $helper;
+ $this->formIds = $formIds;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getSectionData() :array
+ {
+ $data = [];
+
+ foreach ($this->formIds as $formId) {
+ $captchaModel = $this->helper->getCaptcha($formId);
+ $data[$formId] = [
+ 'isRequired' => $captchaModel->isRequired(),
+ 'timestamp' => time()
+ ];
+ }
+
+ return $data;
+ }
+}
diff --git a/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php b/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php
index ef5f5a8edce7..34ee62044ff5 100644
--- a/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php
+++ b/app/code/Magento/Captcha/Model/Checkout/ConfigProvider.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Captcha\Model\Checkout;
+/**
+ * Configuration provider for Captcha rendering.
+ */
class ConfigProvider implements \Magento\Checkout\Model\ConfigProviderInterface
{
/**
@@ -38,7 +41,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getConfig()
{
@@ -49,7 +52,8 @@ public function getConfig()
'imageHeight' => $this->getImageHeight($formId),
'imageSrc' => $this->getImageSrc($formId),
'refreshUrl' => $this->getRefreshUrl(),
- 'isRequired' => $this->isRequired($formId)
+ 'isRequired' => $this->isRequired($formId),
+ 'timestamp' => time()
];
}
return $config;
diff --git a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
index 91f3a785df36..84ac71046c34 100644
--- a/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
+++ b/app/code/Magento/Captcha/Model/Customer/Plugin/AjaxLogin.php
@@ -10,6 +10,9 @@
use Magento\Framework\Session\SessionManagerInterface;
use Magento\Framework\Controller\Result\JsonFactory;
+/**
+ * Around plugin for login action.
+ */
class AjaxLogin
{
/**
@@ -61,6 +64,8 @@ public function __construct(
}
/**
+ * Check captcha data on login action.
+ *
* @param \Magento\Customer\Controller\Ajax\Login $subject
* @param \Closure $proceed
* @return $this
@@ -94,18 +99,20 @@ public function aroundExecute(
if ($formId === $loginFormId) {
$captchaModel = $this->helper->getCaptcha($formId);
if ($captchaModel->isRequired($username)) {
- $captchaModel->logAttempt($username);
if (!$captchaModel->isCorrect($captchaString)) {
$this->sessionManager->setUsername($username);
+ $captchaModel->logAttempt($username);
return $this->returnJsonError(__('Incorrect CAPTCHA'));
}
}
+ $captchaModel->logAttempt($username);
}
}
return $proceed();
}
/**
+ * Format JSON response.
*
* @param \Magento\Framework\Phrase $phrase
* @return \Magento\Framework\Controller\Result\Json
diff --git a/app/code/Magento/Captcha/Model/DefaultModel.php b/app/code/Magento/Captcha/Model/DefaultModel.php
index cf6690df5c85..483f9c3fb4d2 100644
--- a/app/code/Magento/Captcha/Model/DefaultModel.php
+++ b/app/code/Magento/Captcha/Model/DefaultModel.php
@@ -78,6 +78,11 @@ class DefaultModel extends \Zend\Captcha\Image implements \Magento\Captcha\Model
*/
protected $session;
+ /**
+ * @var string
+ */
+ private $words;
+
/**
* @param \Magento\Framework\Session\SessionManagerInterface $session
* @param \Magento\Captcha\Helper\Data $captchaData
@@ -311,18 +316,18 @@ public function getImgUrl()
*/
public function isCorrect($word)
{
- $storedWord = $this->getWord();
+ $storedWords = $this->getWords();
$this->clearWord();
- if (!$word || !$storedWord) {
+ if (!$word || !$storedWords) {
return false;
}
if (!$this->isCaseSensitive()) {
- $storedWord = strtolower($storedWord);
+ $storedWords = strtolower($storedWords);
$word = strtolower($word);
}
- return $word === $storedWord;
+ return in_array($word, explode(',', $storedWords));
}
/**
@@ -481,7 +486,7 @@ private function getTargetForms()
/**
* Get captcha word
*
- * @return string
+ * @return string|null
*/
public function getWord()
{
@@ -489,6 +494,17 @@ public function getWord()
return time() < $sessionData['expires'] ? $sessionData['data'] : null;
}
+ /**
+ * Get captcha words
+ *
+ * @return string|null
+ */
+ private function getWords()
+ {
+ $sessionData = $this->session->getData($this->getFormIdKey(self::SESSION_WORD));
+ return time() < $sessionData['expires'] ? $sessionData['words'] : null;
+ }
+
/**
* Set captcha word
*
@@ -498,9 +514,10 @@ public function getWord()
*/
protected function setWord($word)
{
+ $this->words = $this->words ? $this->words . ',' . $word : $word;
$this->session->setData(
$this->getFormIdKey(self::SESSION_WORD),
- ['data' => $word, 'expires' => time() + $this->getTimeout()]
+ ['data' => $word, 'words' => $this->words, 'expires' => time() + $this->getTimeout()]
);
$this->word = $word;
return $this;
diff --git a/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php b/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php
index 9b97225e60de..39579616fa92 100644
--- a/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php
+++ b/app/code/Magento/Captcha/Observer/CaptchaStringResolver.php
@@ -18,6 +18,6 @@ public function resolve(\Magento\Framework\App\RequestInterface $request, $formI
{
$captchaParams = $request->getPost(\Magento\Captcha\Helper\Data::INPUT_NAME_FIELD_VALUE);
- return isset($captchaParams[$formId]) ? $captchaParams[$formId] : '';
+ return $captchaParams[$formId] ?? '';
}
}
diff --git a/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php b/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
index bdc8dfa21897..dd4974c5d842 100644
--- a/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
+++ b/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Captcha\Observer;
use Magento\Customer\Model\AuthenticationInterface;
@@ -11,7 +12,10 @@
use Magento\Customer\Api\CustomerRepositoryInterface;
/**
+ * Check captcha on user login page observer.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class CheckUserLoginObserver implements ObserverInterface
{
@@ -140,7 +144,7 @@ public function execute(\Magento\Framework\Event\Observer $observer)
$customer = $this->getCustomerRepository()->get($login);
$this->getAuthentication()->processAuthenticationFailure($customer->getId());
} catch (NoSuchEntityException $e) {
- //do nothing as customer existance is validated later in authenticate method
+ //do nothing as customer existence is validated later in authenticate method
}
$this->messageManager->addError(__('Incorrect CAPTCHA'));
$this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml
new file mode 100644
index 000000000000..07329e265987
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml
new file mode 100644
index 000000000000..a371f177e355
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml
new file mode 100644
index 000000000000..aa02588000d2
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml
new file mode 100644
index 000000000000..d800c65cabb6
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml
new file mode 100644
index 000000000000..6c09d1d49381
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml
new file mode 100644
index 000000000000..c68ffbfb5be4
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml
new file mode 100644
index 000000000000..5616b099c026
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
index 71a876bbbcdb..f3b6eb1d9af8 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
@@ -7,13 +7,13 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
-
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml
new file mode 100644
index 000000000000..8aff3d5482f2
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml
new file mode 100644
index 000000000000..3546fa2e57a3
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml
new file mode 100644
index 000000000000..d67ebc1a0076
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml
new file mode 100644
index 000000000000..5ad727a8fe99
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml
new file mode 100644
index 000000000000..90f48c320f2a
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+ customer/captcha/enable
+ 0
+ Yes
+ 1
+
+
+ customer/captcha/enable
+ 0
+ No
+ 0
+
+
+ customer/captcha/forms
+ 0
+ Create user
+ user_create
+
+
+ customer/captcha/forms
+ 0
+ Contact Us
+ contact_us
+
+
+
+ customer/captcha/forms
+ 0
+ Login
+ user_login
+
+
+ customer/captcha/forms
+ 0
+ Change password
+ user_edit
+
+
+
+ customer/captcha/forms
+ 0
+ Forgot password
+ user_forgotpassword
+
+
+ customer/captcha/mode
+ 0
+ Always
+ always
+
+
+
+ customer/captcha/mode
+ 0
+ After number of attempts to login
+ after_fail
+
+
+ customer/captcha/length
+ admin
+ 1
+ 3
+ 3
+
+
+ customer/captcha/symbols
+ admin
+ 1
+ 1
+ 1
+
+
+
+ customer/captcha/length
+ admin
+ 1
+ 4-5
+ 4-5
+
+
+
+ customer/captcha/symbols
+ admin
+ 1
+ ABCDEFGHJKMnpqrstuvwxyz23456789
+ ABCDEFGHJKMnpqrstuvwxyz23456789
+
+
+
+ admin/captcha/enable
+ 0
+ Yes
+ 1
+
+
+ admin/captcha/enable
+ 0
+ No
+ 0
+
+
+ admin/captcha/length
+ admin
+ 1
+ 3
+ 3
+
+
+ admin/captcha/symbols
+ admin
+ 1
+ 1
+ 1
+
+
+
+ admin/captcha/length
+ admin
+ 1
+ 4-5
+ 4-5
+
+
+
+ admin/captcha/symbols
+ admin
+ 1
+ ABCDEFGHJKMnpqrstuvwxyz23456789
+ ABCDEFGHJKMnpqrstuvwxyz23456789
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml
new file mode 100644
index 000000000000..d8fb206b8111
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ WrongCAPTCHA
+
+
+
+
+ 111
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml
index 9db8110c0f64..57a09219fe4d 100644
--- a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
Create user
Login
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml
new file mode 100644
index 000000000000..2bcc6fc542d8
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml
new file mode 100644
index 000000000000..f587812576ff
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml
new file mode 100644
index 000000000000..60cf961ba7e8
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
new file mode 100644
index 000000000000..a273c8d4abd2
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
new file mode 100644
index 000000000000..f48e6124cb21
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
new file mode 100644
index 000000000000..54aa36d1ca26
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml
new file mode 100644
index 000000000000..7a0557c4a274
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml
new file mode 100644
index 000000000000..e5ee55910df6
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml
new file mode 100644
index 000000000000..8f9c5828e2f5
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/AdminResetUserPasswordFailedTest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml
index 8f764899706a..035e58de06cc 100644
--- a/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/CaptchaFormsDisplayingTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
@@ -64,6 +64,52 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml
new file mode 100644
index 000000000000..54237087227d
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml
new file mode 100644
index 000000000000..0c6a3f31c1df
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml
new file mode 100644
index 000000000000..5a1be68d3f25
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml
new file mode 100644
index 000000000000..2c331f958e46
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml
new file mode 100644
index 000000000000..36d7989b9acc
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php b/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php
index 655fcd6118e2..8764dbd4cec1 100644
--- a/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Model/Checkout/ConfigProviderTest.php
@@ -77,6 +77,7 @@ public function testGetConfig($isRequired, $captchaGenerations, $expectedConfig)
->will($this->returnValue('https://magento.com/captcha'));
$config = $this->model->getConfig();
+ unset($config['captcha'][$this->formId]['timestamp']);
$this->assertEquals($config, $expectedConfig);
}
diff --git a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
index 1edbcc029e4c..eef75d2c01ec 100644
--- a/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
+++ b/app/code/Magento/Captcha/Test/Unit/Model/DefaultTest.php
@@ -183,7 +183,13 @@ public function testIsCorrect()
{
self::$_defaultConfig['case_sensitive'] = '1';
$this->assertFalse($this->_object->isCorrect('abcdef5'));
- $sessionData = ['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() + self::EXPIRE_FRAME]];
+ $sessionData = [
+ 'user_create_word' => [
+ 'data' => 'AbCdEf5',
+ 'words' => 'AbCdEf5',
+ 'expires' => time() + self::EXPIRE_FRAME
+ ]
+ ];
$this->_object->getSession()->setData($sessionData);
self::$_defaultConfig['case_sensitive'] = '0';
$this->assertTrue($this->_object->isCorrect('abcdef5'));
@@ -224,7 +230,7 @@ public function testGetWord()
{
$this->assertEquals($this->_object->getWord(), 'AbCdEf5');
$this->_object->getSession()->setData(
- ['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() - 360]]
+ ['user_create_word' => ['data' => 'AbCdEf5', 'words' => 'AbCdEf5','expires' => time() - 360]]
);
$this->assertNull($this->_object->getWord());
}
@@ -247,7 +253,13 @@ protected function _getSessionStub()
->getMock();
$session->expects($this->any())->method('isLoggedIn')->will($this->returnValue(false));
- $session->setData(['user_create_word' => ['data' => 'AbCdEf5', 'expires' => time() + self::EXPIRE_FRAME]]);
+ $session->setData([
+ 'user_create_word' => [
+ 'data' => 'AbCdEf5',
+ 'words' => 'AbCdEf5',
+ 'expires' => time() + self::EXPIRE_FRAME
+ ]
+ ]);
return $session;
}
diff --git a/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginBackendObserverTest.php b/app/code/Magento/Captcha/Test/Unit/Observer/CheckUserLoginBackendObserverTest.php
new file mode 100644
index 000000000000..415f022a7364
--- /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 fa9a14abb896..158e2f43b9f5 100644
--- a/app/code/Magento/Captcha/etc/db_schema.xml
+++ b/app/code/Magento/Captcha/etc/db_schema.xml
@@ -9,11 +9,11 @@
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
-
+
-
+
diff --git a/app/code/Magento/Captcha/etc/di.xml b/app/code/Magento/Captcha/etc/di.xml
index 3a929f5e6cc0..83c4e8aa1e2c 100644
--- a/app/code/Magento/Captcha/etc/di.xml
+++ b/app/code/Magento/Captcha/etc/di.xml
@@ -27,7 +27,7 @@
-
+
diff --git a/app/code/Magento/Captcha/etc/frontend/di.xml b/app/code/Magento/Captcha/etc/frontend/di.xml
index 0c4ab0cda073..490f1eab8519 100644
--- a/app/code/Magento/Captcha/etc/frontend/di.xml
+++ b/app/code/Magento/Captcha/etc/frontend/di.xml
@@ -20,4 +20,18 @@
+
+
+
+ - user_login
+
+
+
+
+
+
+ - Magento\Captcha\CustomerData\Captcha
+
+
+
diff --git a/app/code/Magento/Captcha/etc/frontend/sections.xml b/app/code/Magento/Captcha/etc/frontend/sections.xml
new file mode 100644
index 000000000000..7f2070e10c8a
--- /dev/null
+++ b/app/code/Magento/Captcha/etc/frontend/sections.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/view/frontend/requirejs-config.js b/app/code/Magento/Captcha/view/frontend/requirejs-config.js
index 3b322711f8b1..42c80632d3e9 100644
--- a/app/code/Magento/Captcha/view/frontend/requirejs-config.js
+++ b/app/code/Magento/Captcha/view/frontend/requirejs-config.js
@@ -6,7 +6,8 @@
var config = {
map: {
'*': {
- captcha: 'Magento_Captcha/captcha'
+ captcha: 'Magento_Captcha/js/captcha',
+ 'Magento_Captcha/captcha': 'Magento_Captcha/js/captcha'
}
}
};
diff --git a/app/code/Magento/Captcha/view/frontend/web/captcha.js b/app/code/Magento/Captcha/view/frontend/web/js/captcha.js
similarity index 100%
rename from app/code/Magento/Captcha/view/frontend/web/captcha.js
rename to app/code/Magento/Captcha/view/frontend/web/js/captcha.js
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js b/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js
index 3a235df73a91..e79cfb35ee08 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/model/captcha.js
@@ -17,11 +17,12 @@ define([
imageSource: ko.observable(captchaData.imageSrc),
visibility: ko.observable(false),
captchaValue: ko.observable(null),
- isRequired: captchaData.isRequired,
+ isRequired: ko.observable(captchaData.isRequired),
isCaseSensitive: captchaData.isCaseSensitive,
imageHeight: captchaData.imageHeight,
refreshUrl: captchaData.refreshUrl,
isLoading: ko.observable(false),
+ timestamp: null,
/**
* @return {String}
@@ -41,7 +42,7 @@ define([
* @return {Boolean}
*/
getIsVisible: function () {
- return this.visibility;
+ return this.visibility();
},
/**
@@ -55,14 +56,14 @@ define([
* @return {Boolean}
*/
getIsRequired: function () {
- return this.isRequired;
+ return this.isRequired();
},
/**
* @param {Boolean} flag
*/
setIsRequired: function (flag) {
- this.isRequired = flag;
+ this.isRequired(flag);
},
/**
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
index f80b2ab163ff..d79c42a71156 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
@@ -7,8 +7,10 @@ define([
'jquery',
'uiComponent',
'Magento_Captcha/js/model/captcha',
- 'Magento_Captcha/js/model/captchaList'
-], function ($, Component, Captcha, captchaList) {
+ 'Magento_Captcha/js/model/captchaList',
+ 'Magento_Customer/js/customer-data',
+ 'underscore'
+], function ($, Component, Captcha, captchaList, customerData, _) {
'use strict';
var captchaConfig;
@@ -34,12 +36,49 @@ define([
if (window[this.configSource] && window[this.configSource].captcha) {
captchaConfig = window[this.configSource].captcha;
$.each(captchaConfig, function (formId, captchaData) {
+ var captcha;
+
captchaData.formId = formId;
- captchaList.add(Captcha(captchaData));
- });
+ captcha = Captcha(captchaData);
+ this.checkCustomerData(formId, customerData.get('captcha')(), captcha);
+ this.subscribeCustomerData(formId, captcha);
+ captchaList.add(captcha);
+ }.bind(this));
}
},
+ /**
+ * Check customer data for captcha configuration.
+ *
+ * @param {String} formId
+ * @param {Object} captchaData
+ * @param {Object} captcha
+ */
+ checkCustomerData: function (formId, captchaData, captcha) {
+ if (!_.isEmpty(captchaData) &&
+ !_.isEmpty(captchaData)[formId] &&
+ captchaData[formId].timestamp > captcha.timestamp
+ ) {
+ if (!captcha.isRequired() && captchaData[formId].isRequired) {
+ captcha.refresh();
+ }
+ captcha.isRequired(captchaData[formId].isRequired);
+ captcha.timestamp = captchaData[formId].timestamp;
+ }
+ },
+
+ /**
+ * Subscribe for customer data updates.
+ *
+ * @param {String} formId
+ * @param {Object} captcha
+ */
+ subscribeCustomerData: function (formId, captcha) {
+ customerData.get('captcha').subscribe(function (captchaData) {
+ this.checkCustomerData(formId, captchaData, captcha);
+ }.bind(this));
+ },
+
/**
* @return {Boolean}
*/
@@ -89,6 +128,15 @@ define([
return this.currentCaptcha !== null ? this.currentCaptcha.getIsRequired() : false;
},
+ /**
+ * Set isRequired on current captcha model.
+ *
+ * @param {Boolean} flag
+ */
+ setIsRequired: function (flag) {
+ this.currentCaptcha.setIsRequired(flag);
+ },
+
/**
* @return {Boolean}
*/
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js
index 7709febea60a..49528f6ce850 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/loginCaptcha.js
@@ -6,9 +6,10 @@
define([
'Magento_Captcha/js/view/checkout/defaultCaptcha',
'Magento_Captcha/js/model/captchaList',
- 'Magento_Customer/js/action/login'
+ 'Magento_Customer/js/action/login',
+ 'underscore'
],
-function (defaultCaptcha, captchaList, loginAction) {
+function (defaultCaptcha, captchaList, loginAction, _) {
'use strict';
return defaultCaptcha.extend({
@@ -26,9 +27,10 @@ function (defaultCaptcha, captchaList, loginAction) {
loginAction.registerLoginCallback(function (loginData) {
if (loginData['captcha_form_id'] &&
- loginData['captcha_form_id'] == self.formId //eslint-disable-line eqeqeq
+ loginData['captcha_form_id'] === self.formId &&
+ self.isRequired()
) {
- self.refresh();
+ _.defer(self.refresh.bind(self));
}
});
}
diff --git a/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html b/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html
index 575b3ca6f732..3f48ec330c0a 100644
--- a/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html
+++ b/app/code/Magento/Captcha/view/frontend/web/template/checkout/captcha.html
@@ -4,12 +4,12 @@
* See COPYING.txt for license details.
*/
-->
+
-
diff --git a/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php b/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php
index 30ac06107ba1..a65355c69092 100644
--- a/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php
+++ b/app/code/Magento/Catalog/Api/CategoryLinkRepositoryInterface.php
@@ -32,18 +32,20 @@ public function save(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $pro
*
* @throws \Magento\Framework\Exception\CouldNotSaveException
* @throws \Magento\Framework\Exception\StateException
+ * @throws \Magento\Framework\Exception\InputException
*/
public function delete(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $productLink);
/**
* Remove the product assignment from the category by category id and sku
*
- * @param string $sku
+ * @param int $categoryId
* @param string $sku
* @return bool will returned True if products successfully deleted
*
* @throws \Magento\Framework\Exception\CouldNotSaveException
* @throws \Magento\Framework\Exception\StateException
+ * @throws \Magento\Framework\Exception\InputException
*/
public function deleteByIds($categoryId, $sku);
}
diff --git a/app/code/Magento/Catalog/Api/Data/CategoryInterface.php b/app/code/Magento/Catalog/Api/Data/CategoryInterface.php
index b9a23e9d08ec..1940a0ac80c0 100644
--- a/app/code/Magento/Catalog/Api/Data/CategoryInterface.php
+++ b/app/code/Magento/Catalog/Api/Data/CategoryInterface.php
@@ -1,7 +1,5 @@
getNodeById($rootId);
- if ($root && $rootId != \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
+ if ($root) {
$root->setIsVisible(true);
- } elseif ($root && $root->getId() == \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
- $root->setName(__('Root'));
+ if ($root->getId() == \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
+ $root->setName(__('Root'));
+ }
}
$this->_coreRegistry->register('root', $root);
@@ -162,6 +175,8 @@ public function getRoot($parentNodeCategory = null, $recursionLevel = 3)
}
/**
+ * Get Default Store Id
+ *
* @return int
*/
protected function _getDefaultStoreId()
@@ -170,6 +185,8 @@ protected function _getDefaultStoreId()
}
/**
+ * Get category collection
+ *
* @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
*/
public function getCategoryCollection()
@@ -227,6 +244,8 @@ public function getRootByIds($ids)
}
/**
+ * Get category node for tree
+ *
* @param mixed $parentNodeCategory
* @param int $recursionLevel
* @return Node
@@ -249,6 +268,8 @@ public function getNode($parentNodeCategory, $recursionLevel = 2)
}
/**
+ * Get category save url
+ *
* @param array $args
* @return string
*/
@@ -260,6 +281,8 @@ public function getSaveUrl(array $args = [])
}
/**
+ * Get category edit url
+ *
* @return string
*/
public function getEditUrl()
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Edit/DeleteButton.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Edit/DeleteButton.php
index 20411a4c4d76..2eef1188e391 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Edit/DeleteButton.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Edit/DeleteButton.php
@@ -27,7 +27,8 @@ public function getButtonData()
return [
'id' => 'delete',
'label' => __('Delete'),
- 'on_click' => "categoryDelete('" . $this->getDeleteUrl() . "')",
+ 'on_click' => "deleteConfirm('" .__('Are you sure you want to delete this category?') ."', '"
+ . $this->getDeleteUrl() . "', {data: {}})",
'class' => 'delete',
'sort_order' => 10
];
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php
index b77a5e2e9524..3266922d116e 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Helper/Pricestep.php
@@ -11,6 +11,9 @@
*/
namespace Magento\Catalog\Block\Adminhtml\Category\Helper;
+/**
+ * Pricestep Helper
+ */
class Pricestep extends \Magento\Framework\Data\Form\Element\Text
{
/**
@@ -40,7 +43,7 @@ public function getElementHtml()
$disabled = true;
}
- parent::addClass('validate-number validate-number-range number-range-0.01-1000000000');
+ parent::addClass('validate-number validate-number-range number-range-0.01-9999999999999999');
$html = parent::getElementHtml();
$htmlId = 'use_config_' . $this->getHtmlId();
$html .= ' getLevel() < 2) {
+ if ($isParent || $node->getLevel() < 1) {
$item['expanded'] = true;
}
@@ -390,6 +414,8 @@ public function buildNodeName($node)
}
/**
+ * Is category movable
+ *
* @param Node|array $node
* @return bool
*/
@@ -403,6 +429,8 @@ protected function _isCategoryMoveable($node)
}
/**
+ * Is parent selected category
+ *
* @param Node|array $node
* @return bool
*/
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Fieldset/Element.php b/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Fieldset/Element.php
index ad6df27b8933..8f1d1dcf7eed 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Fieldset/Element.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Fieldset/Element.php
@@ -4,13 +4,13 @@
* See COPYING.txt for license details.
*/
+namespace Magento\Catalog\Block\Adminhtml\Form\Renderer\Fieldset;
+
/**
* Catalog fieldset element renderer
*
* @author Magento Core Team
*/
-namespace Magento\Catalog\Block\Adminhtml\Form\Renderer\Fieldset;
-
class Element extends \Magento\Backend\Block\Widget\Form\Renderer\Fieldset\Element
{
/**
@@ -29,7 +29,7 @@ public function getDataObject()
}
/**
- * Retireve associated with element attribute object
+ * Retrieve associated with element attribute object
*
* @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute
*/
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
index dd09e40ac5b3..1b6756968662 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
@@ -4,11 +4,6 @@
* See COPYING.txt for license details.
*/
-/**
- * Product attribute add/edit form main tab
- *
- * @author Magento Core Team
- */
namespace Magento\Catalog\Block\Adminhtml\Product\Attribute\Edit\Tab;
use Magento\Backend\Block\Widget\Form\Generic;
@@ -18,6 +13,8 @@
use Magento\Framework\App\ObjectManager;
/**
+ * Product attribute add/edit form main tab
+ *
* @api
* @since 100.0.2
*/
@@ -73,6 +70,7 @@ public function __construct(
* Adding product form elements for editing attribute
*
* @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD)
*/
protected function _prepareForm()
@@ -255,7 +253,7 @@ protected function _prepareForm()
}
/**
- * Initialize form fileds values
+ * Initialize form fields values
*
* @return $this
*/
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php
index 1b188de40710..3b9036c1fbbc 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main.php
@@ -6,13 +6,13 @@
namespace Magento\Catalog\Block\Adminhtml\Product\Attribute\Set;
/**
- * Adminhtml Catalog Attribute Set Main Block
- *
* @author Magento Core Team
*/
use Magento\Catalog\Model\Entity\Product\Attribute\Group\AttributeMapperInterface;
/**
+ * Adminhtml Catalog Attribute Set Main Block.
+ *
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
@@ -140,7 +140,7 @@ protected function _prepareLayout()
) . '\', \'' . $this->getUrl(
'catalog/*/delete',
['id' => $setId]
- ) . '\')',
+ ) . '\',{data: {}})',
'class' => 'delete'
]
);
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php
index ee92fd7c19b8..26ffc6e0df3d 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 4310f61c5716..2df0ff0b6cd7 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php
@@ -9,13 +9,18 @@
*
* @author Magento Core Team
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Action\Attribute\Tab;
use Magento\Framework\Data\Form\Element\AbstractElement;
/**
+ * Attributes tab block
+ *
* @api
* @SuppressWarnings(PHPMD.DepthOfInheritance)
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
*/
class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements
@@ -31,6 +36,9 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements
*/
protected $_attributeAction;
+ /** @var array */
+ private $excludeFields;
+
/**
* @param \Magento\Backend\Block\Template\Context $context
* @param \Magento\Framework\Registry $registry
@@ -38,6 +46,7 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements
* @param \Magento\Catalog\Model\ProductFactory $productFactory
* @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeAction
* @param array $data
+ * @param array|null $excludeFields
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
@@ -45,37 +54,25 @@ public function __construct(
\Magento\Framework\Data\FormFactory $formFactory,
\Magento\Catalog\Model\ProductFactory $productFactory,
\Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeAction,
- array $data = []
+ array $data = [],
+ array $excludeFields = null
) {
$this->_attributeAction = $attributeAction;
$this->_productFactory = $productFactory;
- parent::__construct($context, $registry, $formFactory, $data);
- }
+ $this->excludeFields = $excludeFields ?: [];
- /**
- * @return void
- */
- protected function _construct()
- {
- parent::_construct();
- $this->setShowGlobalIcon(true);
+ parent::__construct($context, $registry, $formFactory, $data);
}
/**
+ * Prepares form
+ *
* @return void
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
- protected function _prepareForm()
+ protected function _prepareForm(): void
{
- $this->setFormExcludedFieldList(
- [
- 'category_ids',
- 'gallery',
- 'image',
- 'media_gallery',
- 'quantity_and_stock_status',
- 'tier_price',
- ]
- );
+ $this->setFormExcludedFieldList($this->getExcludedFields());
$this->_eventManager->dispatch(
'adminhtml_catalog_product_form_prepare_excluded_field_list',
['object' => $this]
@@ -149,12 +146,14 @@ protected function _getAdditionalElementHtml($element)
weightHandle.hideWeightSwitcher();
});
HTML;
- // @codingStandardsIgnoreEnd
+ // @codingStandardsIgnoreEnd
}
return $html;
}
/**
+ * Returns tab label
+ *
* @return \Magento\Framework\Phrase
*/
public function getTabLabel()
@@ -163,6 +162,8 @@ public function getTabLabel()
}
/**
+ * Return Tab title
+ *
* @return \Magento\Framework\Phrase
*/
public function getTabTitle()
@@ -171,6 +172,8 @@ public function getTabTitle()
}
/**
+ * Can show tab in tabs
+ *
* @return bool
*/
public function canShowTab()
@@ -179,10 +182,22 @@ public function canShowTab()
}
/**
+ * Tab not hidden
+ *
* @return bool
*/
public function isHidden()
{
return false;
}
+
+ /**
+ * Returns excluded fields
+ *
+ * @return array
+ */
+ private function getExcludedFields(): array
+ {
+ return $this->excludeFields;
+ }
}
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php
index 750bf6f8a021..964872b6e51b 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php
@@ -70,11 +70,11 @@ public function getFieldSuffix()
* Retrieve current store id
*
* @return int
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getStoreId()
{
- $storeId = $this->getRequest()->getParam('store');
- return intval($storeId);
+ return (int)$this->getRequest()->getParam('store');
}
/**
@@ -99,6 +99,8 @@ public function getTabLabel()
}
/**
+ * Return Tab title.
+ *
* @return \Magento\Framework\Phrase
*/
public function getTabTitle()
@@ -107,7 +109,7 @@ public function getTabTitle()
}
/**
- * @return bool
+ * @inheritdoc
*/
public function canShowTab()
{
@@ -115,7 +117,7 @@ public function canShowTab()
}
/**
- * @return bool
+ * @inheritdoc
*/
public function isHidden()
{
@@ -123,6 +125,8 @@ public function isHidden()
}
/**
+ * Get availability status.
+ *
* @param string $fieldName
* @return bool
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php
index e4f700e5790a..e5ce59c550af 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Crosssell.php
@@ -10,9 +10,13 @@
use Magento\Catalog\Model\Product;
/**
+ * Crossel product edit tab
+ *
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
+ * @deprecated Not used since cross-sell products grid moved to UI components.
+ * @see \Magento\Catalog\Ui\DataProvider\Product\Related\CrossSellDataProvider
*/
class Crosssell extends Extended
{
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php
index 0c59a7402dac..23b927598e8e 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Related.php
@@ -9,8 +9,12 @@
use Magento\Backend\Block\Widget\Grid\Extended;
/**
+ * Related product edit tab
+ *
* @api
* @since 100.0.2
+ * @deprecated Not used since related products grid moved to UI components.
+ * @see \Magento\Catalog\Ui\DataProvider\Product\Related\RelatedDataProvider
*/
class Related extends Extended
{
@@ -318,7 +322,7 @@ protected function _prepareColumns()
}
/**
- * Rerieve grid URL
+ * Retrieve grid URL
*
* @return string
*/
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php
index 323b1785bc96..41ad72ca39e5 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Upsell.php
@@ -6,8 +6,12 @@
namespace Magento\Catalog\Block\Adminhtml\Product\Edit\Tab;
/**
+ * Upsell product edit tab
+ *
* @api
* @since 100.0.2
+ * @deprecated Not used since upsell products grid moved to UI components.
+ * @see \Magento\Catalog\Ui\DataProvider\Product\Related\CrossSellDataProvider
*/
class Upsell extends \Magento\Backend\Block\Widget\Grid\Extended
{
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php
index 04c3a208b97f..37ad3f4bea20 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tabs.php
@@ -8,7 +8,7 @@
use Magento\Backend\Block\Template\Context;
use Magento\Backend\Block\Widget\Accordion;
-use Magento\Backend\Block\Widget\Tabs as WigetTabs;
+use Magento\Backend\Block\Widget\Tabs as WidgetTabs;
use Magento\Backend\Model\Auth\Session;
use Magento\Catalog\Helper\Catalog;
use Magento\Catalog\Helper\Data;
@@ -22,7 +22,7 @@
* Admin product edit tabs
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Tabs extends WigetTabs
+class Tabs extends WidgetTabs
{
const BASIC_TAB_GROUP_CODE = 'basic';
@@ -109,7 +109,7 @@ public function __construct(
}
/**
- * @return void
+ * @inheritdoc
*/
protected function _construct()
{
@@ -119,6 +119,8 @@ protected function _construct()
}
/**
+ * Get group collection.
+ *
* @param int $attributeSetId
* @return \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\Collection
*/
@@ -131,10 +133,11 @@ public function getGroupCollection($attributeSetId)
}
/**
- * @return $this
+ * @inheritdoc
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
protected function _prepareLayout()
{
@@ -315,6 +318,8 @@ public function getAttributeTabBlock()
}
/**
+ * Set attribute tab block.
+ *
* @param string $attributeTabBlock
* @return $this
*/
@@ -337,6 +342,8 @@ protected function _translateHtml($html)
}
/**
+ * Get accordion.
+ *
* @param string $parentTab
* @return string
*/
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Frontend/Product/Watermark.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Frontend/Product/Watermark.php
index 1f74969c3d16..7f80aece60ee 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Frontend/Product/Watermark.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Frontend/Product/Watermark.php
@@ -4,15 +4,15 @@
* See COPYING.txt for license details.
*/
+namespace Magento\Catalog\Block\Adminhtml\Product\Frontend\Product;
+
+use Magento\Framework\Data\Form\Element\AbstractElement;
+
/**
* Fieldset config form element renderer
*
* @author Magento Core Team
*/
-namespace Magento\Catalog\Block\Adminhtml\Product\Frontend\Product;
-
-use Magento\Framework\Data\Form\Element\AbstractElement;
-
class Watermark extends \Magento\Backend\Block\AbstractBlock implements
\Magento\Framework\Data\Form\Element\Renderer\RendererInterface
{
@@ -60,6 +60,8 @@ public function __construct(
}
/**
+ * Render form element as HTML
+ *
* @param AbstractElement $element
* @return string
*/
@@ -124,13 +126,14 @@ public function render(AbstractElement $element)
}
/**
+ * Get header html for render
+ *
* @param AbstractElement $element
* @return string
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
*/
protected function _getHeaderHtml($element)
{
- $id = $element->getHtmlId();
$default = !$this->getRequest()->getParam('website') && !$this->getRequest()->getParam('store');
$html = '';
@@ -148,6 +151,8 @@ protected function _getHeaderHtml($element)
}
/**
+ * Get footer html for render
+ *
* @param AbstractElement $element
* @return string
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php
index 6cb6f0e526e9..36740f5853d7 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php
@@ -11,11 +11,16 @@
*/
namespace Magento\Catalog\Block\Adminhtml\Product\Helper\Form;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Registry;
use Magento\Catalog\Model\Product;
use Magento\Eav\Model\Entity\Attribute;
use Magento\Catalog\Api\Data\ProductInterface;
+/**
+ * Adminhtml gallery block
+ */
class Gallery extends \Magento\Framework\View\Element\AbstractBlock
{
/**
@@ -66,27 +71,37 @@ class Gallery extends \Magento\Framework\View\Element\AbstractBlock
*/
protected $registry;
+ /**
+ * @var DataPersistorInterface
+ */
+ private $dataPersistor;
+
/**
* @param \Magento\Framework\View\Element\Context $context
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param Registry $registry
* @param \Magento\Framework\Data\Form $form
* @param array $data
+ * @param DataPersistorInterface|null $dataPersistor
*/
public function __construct(
\Magento\Framework\View\Element\Context $context,
\Magento\Store\Model\StoreManagerInterface $storeManager,
Registry $registry,
\Magento\Framework\Data\Form $form,
- $data = []
+ $data = [],
+ DataPersistorInterface $dataPersistor = null
) {
$this->storeManager = $storeManager;
$this->registry = $registry;
$this->form = $form;
+ $this->dataPersistor = $dataPersistor ?: ObjectManager::getInstance()->get(DataPersistorInterface::class);
parent::__construct($context, $data);
}
/**
+ * Returns element html.
+ *
* @return string
*/
public function getElementHtml()
@@ -102,7 +117,24 @@ public function getElementHtml()
*/
public function getImages()
{
- return $this->getDataObject()->getData('media_gallery') ?: null;
+ $images = $this->getDataObject()->getData('media_gallery') ?: null;
+ if ($images === null) {
+ $images = ((array)$this->dataPersistor->get('catalog_product'))['product']['media_gallery'] ?? null;
+ }
+
+ return $images;
+ }
+
+ /**
+ * Get value for given type.
+ *
+ * @param string $type
+ * @return string|null
+ */
+ public function getImageValue(string $type)
+ {
+ $product = (array)$this->dataPersistor->get('catalog_product');
+ return $product['product'][$type] ?? null;
}
/**
@@ -122,6 +154,8 @@ public function getContentHtml()
}
/**
+ * Returns html id
+ *
* @return string
*/
protected function getHtmlId()
@@ -130,6 +164,8 @@ protected function getHtmlId()
}
/**
+ * Returns name
+ *
* @return string
*/
public function getName()
@@ -138,6 +174,8 @@ public function getName()
}
/**
+ * Returns suffix for field name
+ *
* @return string
*/
public function getFieldNameSuffix()
@@ -146,6 +184,8 @@ public function getFieldNameSuffix()
}
/**
+ * Returns data scope html id
+ *
* @return string
*/
public function getDataScopeHtmlId()
@@ -230,7 +270,6 @@ public function getDataObject()
/**
* Retrieve attribute field name
*
- *
* @param Attribute $attribute
* @return string
*/
@@ -244,6 +283,8 @@ public function getAttributeFieldName($attribute)
}
/**
+ * Returns html content of the block
+ *
* @return string
*/
public function toHtml()
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 8d51f78ff5b6..063503682f4d 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,11 +13,16 @@
*/
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.
+ */
class Content extends \Magento\Backend\Block\Widget
{
/**
@@ -40,29 +45,44 @@ 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);
}
/**
+ * Prepare layout.
+ *
* @return AbstractBlock
*/
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')
@@ -103,6 +123,8 @@ public function getUploaderHtml()
}
/**
+ * Returns js object name
+ *
* @return string
*/
public function getJsObjectName()
@@ -111,6 +133,8 @@ public function getJsObjectName()
}
/**
+ * Returns buttons for add image action.
+ *
* @return string
*/
public function getAddImagesButton()
@@ -124,6 +148,8 @@ public function getAddImagesButton()
}
/**
+ * Returns image json
+ *
* @return string
*/
public function getImagesJson()
@@ -169,6 +195,8 @@ private function sortImagesByPosition($images)
}
/**
+ * Returns image values json
+ *
* @return string
*/
public function getImagesValuesJson()
@@ -193,9 +221,11 @@ public function getImageTypes()
$imageTypes = [];
foreach ($this->getMediaAttributes() as $attribute) {
/* @var $attribute \Magento\Eav\Model\Entity\Attribute */
+ $value = $this->getElement()->getDataObject()->getData($attribute->getAttributeCode())
+ ?: $this->getElement()->getImageValue($attribute->getAttributeCode());
$imageTypes[$attribute->getAttributeCode()] = [
'code' => $attribute->getAttributeCode(),
- 'value' => $this->getElement()->getDataObject()->getData($attribute->getAttributeCode()),
+ 'value' => $value,
'label' => $attribute->getFrontend()->getLabel(),
'scope' => __($this->getElement()->getScopeLabel($attribute)),
'name' => $this->getElement()->getAttributeFieldName($attribute),
@@ -241,6 +271,8 @@ public function getImageTypesJson()
}
/**
+ * Returns image helper object.
+ *
* @return \Magento\Catalog\Helper\Image
* @deprecated 101.0.3
*/
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Rss/NotifyStock.php b/app/code/Magento/Catalog/Block/Adminhtml/Rss/NotifyStock.php
index ac1fd8c692ed..c296a5aa0dbb 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Rss/NotifyStock.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Rss/NotifyStock.php
@@ -9,6 +9,7 @@
/**
* Class NotifyStock
+ *
* @package Magento\Catalog\Block\Adminhtml\Rss
*/
class NotifyStock extends \Magento\Backend\Block\AbstractBlock implements DataProviderInterface
@@ -41,7 +42,7 @@ public function __construct(
}
/**
- * @return void
+ * @inheritdoc
*/
protected function _construct()
{
@@ -50,12 +51,12 @@ protected function _construct()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getRssData()
{
- $newUrl = $this->rssUrlBuilder->getUrl(['_secure' => true, '_nosecret' => true, 'type' => 'notifystock']);
- $title = __('Low Stock Products');
+ $newUrl = $this->rssUrlBuilder->getUrl(['_secure' => true, '_nosecret' => true, 'type' => 'notifystock']);
+ $title = __('Low Stock Products')->render();
$data = ['title' => $title, 'description' => $title, 'link' => $newUrl, 'charset' => 'UTF-8'];
foreach ($this->rssModel->getProductsCollection() as $item) {
@@ -65,7 +66,7 @@ public function getRssData()
['id' => $item->getId(), '_secure' => true, '_nosecret' => true]
);
$qty = 1 * $item->getQty();
- $description = __('%1 has reached a quantity of %2.', $item->getName(), $qty);
+ $description = __('%1 has reached a quantity of %2.', $item->getName(), $qty)->render();
$data['entries'][] = ['title' => $item->getName(), 'link' => $url, 'description' => $description];
}
@@ -73,7 +74,7 @@ public function getRssData()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getCacheLifetime()
{
@@ -81,7 +82,7 @@ public function getCacheLifetime()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function isAllowed()
{
@@ -89,7 +90,7 @@ public function isAllowed()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getFeeds()
{
@@ -97,7 +98,7 @@ public function getFeeds()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function isAuthRequired()
{
diff --git a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php
index 4102c82a0a31..c8da0f70f73b 100644
--- a/app/code/Magento/Catalog/Block/Product/AbstractProduct.php
+++ b/app/code/Magento/Catalog/Block/Product/AbstractProduct.php
@@ -125,6 +125,7 @@ public function __construct(\Magento\Catalog\Block\Product\Context $context, arr
/**
* Retrieve url for add product to cart
+ *
* Will return product view page URL if product has required options
*
* @param \Magento\Catalog\Model\Product $product
@@ -473,7 +474,9 @@ public function getProductDetailsHtml(\Magento\Catalog\Model\Product $product)
}
/**
- * @param null $type
+ * Get the renderer that will be used to render the details block
+ *
+ * @param string|null $type
* @return bool|\Magento\Framework\View\Element\AbstractBlock
*/
public function getDetailsRenderer($type = null)
@@ -489,6 +492,8 @@ public function getDetailsRenderer($type = null)
}
/**
+ * Return the list of details
+ *
* @return \Magento\Framework\View\Element\RendererList
*/
protected function getDetailsRendererList()
diff --git a/app/code/Magento/Catalog/Block/Product/Compare/ListCompare.php b/app/code/Magento/Catalog/Block/Product/Compare/ListCompare.php
index 6c54aa4e171e..76f5dbd1bea8 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 20a556ab4145..7a7f9c0affc7 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 f9a576367dde..aa303af656a5 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/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php
index c1b255c762db..c1d79894162a 100644
--- a/app/code/Magento/Catalog/Block/Product/ListProduct.php
+++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php
@@ -178,8 +178,9 @@ private function getDefaultListingMode()
}
/**
- * Need use as _prepareLayout - but problem in declaring collection from
- * another block (was problem with search result)
+ * Need use as _prepareLayout - but problem in declaring collection from another block.
+ * (was problem with search result)
+ *
* @return $this
*/
protected function _beforeToHtml()
@@ -188,7 +189,9 @@ protected function _beforeToHtml()
$this->addToolbarBlock($collection);
- $collection->load();
+ if (!$collection->isLoaded()) {
+ $collection->load();
+ }
return parent::_beforeToHtml();
}
@@ -262,6 +265,8 @@ public function getToolbarHtml()
}
/**
+ * Set collection.
+ *
* @param AbstractCollection $collection
* @return $this
*/
@@ -272,7 +277,9 @@ public function setCollection($collection)
}
/**
- * @param array|string|integer| Element $code
+ * Add attribute.
+ *
+ * @param array|string|integer|Element $code
* @return $this
*/
public function addAttribute($code)
@@ -282,6 +289,8 @@ public function addAttribute($code)
}
/**
+ * Get price block template.
+ *
* @return mixed
*/
public function getPriceBlockTemplate()
@@ -371,6 +380,8 @@ public function getAddToCartPostParams(Product $product)
}
/**
+ * Get product price.
+ *
* @param Product $product
* @return string
*/
@@ -396,8 +407,8 @@ public function getProductPrice(Product $product)
}
/**
- * Specifies that price rendering should be done for the list of products
- * i.e. rendering happens in the scope of product list, but not single product
+ * Specifies that price rendering should be done for the list of products.
+ * (rendering happens in the scope of product list, but not single product)
*
* @return Render
*/
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
index 0c547f81c85d..596cd7cc5bdc 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Crosssell.php
@@ -9,6 +9,9 @@
*/
namespace Magento\Catalog\Block\Product\ProductList;
+/**
+ * Crosssell block for product
+ */
class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct
{
/**
@@ -25,7 +28,7 @@ class Crosssell extends \Magento\Catalog\Block\Product\AbstractProduct
*/
protected function _prepareData()
{
- $product = $this->_coreRegistry->registry('product');
+ $product = $this->getProduct();
/* @var $product \Magento\Catalog\Model\Product */
$this->_itemCollection = $product->getCrossSellProductCollection()->addAttributeToSelect(
@@ -43,6 +46,7 @@ protected function _prepareData()
/**
* Before rendering html process
+ *
* Prepare items collection
*
* @return \Magento\Catalog\Block\Product\ProductList\Crosssell
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
index 219922f9e46d..6de70bb97136 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php
@@ -77,11 +77,13 @@ public function __construct(
}
/**
+ * Prepare data
+ *
* @return $this
*/
protected function _prepareData()
{
- $product = $this->_coreRegistry->registry('product');
+ $product = $this->getProduct();
/* @var $product \Magento\Catalog\Model\Product */
$this->_itemCollection = $product->getRelatedProductCollection()->addAttributeToSelect(
@@ -103,6 +105,8 @@ protected function _prepareData()
}
/**
+ * Before to html handler
+ *
* @return $this
*/
protected function _beforeToHtml()
@@ -112,6 +116,8 @@ protected function _beforeToHtml()
}
/**
+ * Get collection items
+ *
* @return Collection
*/
public function getItems()
diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php b/app/code/Magento/Catalog/Block/Product/ProductList/Toolbar.php
index 0b8d2d6c89e7..c530ba4785ad 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 10aebf270579..24822447ae91 100644
--- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
+++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php
@@ -7,7 +7,6 @@
namespace Magento\Catalog\Block\Product\ProductList;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
-use Magento\Framework\View\Element\AbstractBlock;
/**
* Catalog product upsell items block
@@ -92,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')) {
@@ -122,6 +123,8 @@ protected function _prepareData()
}
/**
+ * Before to html handler
+ *
* @return $this
*/
protected function _beforeToHtml()
@@ -131,6 +134,8 @@ protected function _beforeToHtml()
}
/**
+ * Get items collection
+ *
* @return Collection
*/
public function getItemCollection()
@@ -146,6 +151,8 @@ public function getItemCollection()
}
/**
+ * Get collection items
+ *
* @return \Magento\Framework\DataObject[]
*/
public function getItems()
@@ -157,6 +164,8 @@ public function getItems()
}
/**
+ * Get row count
+ *
* @return float
*/
public function getRowCount()
@@ -165,18 +174,22 @@ public function getRowCount()
}
/**
+ * Set column count
+ *
* @param string $columns
* @return $this
*/
public function setColumnCount($columns)
{
- if (intval($columns) > 0) {
- $this->_columnCount = intval($columns);
+ if ((int) $columns > 0) {
+ $this->_columnCount = (int) $columns;
}
return $this;
}
/**
+ * Get column count
+ *
* @return int
*/
public function getColumnCount()
@@ -185,6 +198,8 @@ public function getColumnCount()
}
/**
+ * Reset items iterator
+ *
* @return void
*/
public function resetItemsIterator()
@@ -194,6 +209,8 @@ public function resetItemsIterator()
}
/**
+ * Get iterable item
+ *
* @return mixed
*/
public function getIterableItem()
@@ -205,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
@@ -213,13 +231,15 @@ public function getIterableItem()
*/
public function setItemLimit($type, $limit)
{
- if (intval($limit) > 0) {
- $this->_itemLimits[$type] = intval($limit);
+ if ((int) $limit > 0) {
+ $this->_itemLimits[$type] = (int) $limit;
}
return $this;
}
/**
+ * Get item limit
+ *
* @param string $type
* @return array|int
*/
diff --git a/app/code/Magento/Catalog/Block/Product/View/Attributes.php b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
index cb59d86a7451..5b9777cbfd1e 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Attributes.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
@@ -16,6 +16,8 @@
use Magento\Framework\Pricing\PriceCurrencyInterface;
/**
+ * Attributes attributes block
+ *
* @api
* @since 100.0.2
*/
@@ -56,6 +58,8 @@ public function __construct(
}
/**
+ * Returns a Product
+ *
* @return Product
*/
public function getProduct()
@@ -88,9 +92,9 @@ public function getAdditionalData(array $excludeAttr = [])
$value = $this->priceCurrency->convertAndFormat($value);
}
- if (is_string($value) && strlen($value)) {
+ if (is_string($value) && strlen(trim($value))) {
$data[$attribute->getAttributeCode()] = [
- 'label' => __($attribute->getStoreLabel()),
+ 'label' => $attribute->getStoreLabel(),
'value' => $value,
'code' => $attribute->getAttributeCode(),
];
diff --git a/app/code/Magento/Catalog/Block/Product/View/Details.php b/app/code/Magento/Catalog/Block/Product/View/Details.php
new file mode 100644
index 000000000000..e76c5bf20133
--- /dev/null
+++ b/app/code/Magento/Catalog/Block/Product/View/Details.php
@@ -0,0 +1,47 @@
+getGroupChildNames($groupName, $callback);
+ $layout = $this->getLayout();
+
+ $childNamesSortOrder = [];
+
+ foreach ($groupChildNames as $childName) {
+ $alias = $layout->getElementAlias($childName);
+ $sortOrder = (int)$this->getChildData($alias, 'sort_order') ?? 0;
+
+ $childNamesSortOrder[$sortOrder] = $childName;
+ }
+
+ ksort($childNamesSortOrder, SORT_NUMERIC);
+
+ return $childNamesSortOrder;
+ }
+}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Gallery.php b/app/code/Magento/Catalog/Block/Product/View/Gallery.php
index ab01fc6d134e..8b98fbdc8f7e 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Gallery.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Gallery.php
@@ -23,6 +23,8 @@
use Magento\Framework\Stdlib\ArrayUtils;
/**
+ * Product gallery block
+ *
* @api
* @since 100.0.2
*/
@@ -139,7 +141,7 @@ public function getGalleryImagesJson()
'thumb' => $image->getData('small_image_url'),
'img' => $image->getData('medium_image_url'),
'full' => $image->getData('large_image_url'),
- 'caption' => $image->getData('label'),
+ 'caption' => ($image->getLabel() ?: $this->getProduct()->getName()),
'position' => $image->getData('position'),
'isMain' => $this->isMainImage($image),
'type' => str_replace('external-', '', $image->getMediaType()),
@@ -196,6 +198,8 @@ public function isMainImage($image)
}
/**
+ * Returns image attribute
+ *
* @param string $imageId
* @param string $attributeName
* @param string $default
@@ -203,9 +207,9 @@ public function isMainImage($image)
*/
public function getImageAttribute($imageId, $attributeName, $default = null)
{
- $attributes =
- $this->getConfigView()->getMediaAttributes('Magento_Catalog', Image::MEDIA_TYPE_CONFIG_NODE, $imageId);
- return isset($attributes[$attributeName]) ? $attributes[$attributeName] : $default;
+ $attributes = $this->getConfigView()
+ ->getMediaAttributes('Magento_Catalog', Image::MEDIA_TYPE_CONFIG_NODE, $imageId);
+ return $attributes[$attributeName] ?? $default;
}
/**
@@ -222,6 +226,8 @@ private function getConfigView()
}
/**
+ * Returns image gallery config object
+ *
* @return Collection
*/
private function getGalleryImagesConfig()
diff --git a/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php b/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php
new file mode 100644
index 000000000000..0384c9cd9acc
--- /dev/null
+++ b/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php
@@ -0,0 +1,156 @@
+gallery = $gallery;
+ $this->jsonSerializer = $jsonSerializer;
+ parent::__construct($context, $arrayUtils, $data);
+ }
+
+ /**
+ * Retrieve gallery options in JSON format
+ *
+ * @return string
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.ElseExpression)
+ */
+ public function getOptionsJson()
+ {
+ $optionItems = null;
+
+ //Special case for gallery/nav which can be the string "thumbs/false/dots"
+ if (is_bool($this->getVar("gallery/nav"))) {
+ $optionItems['nav'] = $this->getVar("gallery/nav") ? 'true' : 'false';
+ } else {
+ $optionItems['nav'] = $this->escapeHtml($this->getVar("gallery/nav"));
+ }
+
+ $optionItems['loop'] = $this->getVar("gallery/loop");
+ $optionItems['keyboard'] = $this->getVar("gallery/keyboard");
+ $optionItems['arrows'] = $this->getVar("gallery/arrows");
+ $optionItems['allowfullscreen'] = $this->getVar("gallery/allowfullscreen");
+ $optionItems['showCaption'] = $this->getVar("gallery/caption");
+ $optionItems['width'] = (int)$this->escapeHtml(
+ $this->gallery->getImageAttribute('product_page_image_medium', 'width')
+ );
+ $optionItems['thumbwidth'] = (int)$this->escapeHtml(
+ $this->gallery->getImageAttribute('product_page_image_small', 'width')
+ );
+
+ if ($this->gallery->getImageAttribute('product_page_image_small', 'height') ||
+ $this->gallery->getImageAttribute('product_page_image_small', 'width')) {
+ $optionItems['thumbheight'] = (int)$this->escapeHtml(
+ $this->gallery->getImageAttribute('product_page_image_small', 'height') ?:
+ $this->gallery->getImageAttribute('product_page_image_small', 'width')
+ );
+ }
+
+ if ($this->gallery->getImageAttribute('product_page_image_medium', 'height') ||
+ $this->gallery->getImageAttribute('product_page_image_medium', 'width')) {
+ $optionItems['height'] = (int)$this->escapeHtml(
+ $this->gallery->getImageAttribute('product_page_image_medium', 'height') ?:
+ $this->gallery->getImageAttribute('product_page_image_medium', 'width')
+ );
+ }
+
+ if ($this->getVar("gallery/transition/duration")) {
+ $optionItems['transitionduration'] =
+ (int)$this->escapeHtml($this->getVar("gallery/transition/duration"));
+ }
+
+ $optionItems['transition'] = $this->escapeHtml($this->getVar("gallery/transition/effect"));
+ $optionItems['navarrows'] = $this->getVar("gallery/navarrows");
+ $optionItems['navtype'] = $this->escapeHtml($this->getVar("gallery/navtype"));
+ $optionItems['navdir'] = $this->escapeHtml($this->getVar("gallery/navdir"));
+
+ if ($this->getVar("gallery/thumbmargin")) {
+ $optionItems['thumbmargin'] = (int)$this->escapeHtml($this->getVar("gallery/thumbmargin"));
+ }
+
+ return $this->jsonSerializer->serialize($optionItems);
+ }
+
+ /**
+ * Retrieve gallery fullscreen options in JSON format
+ *
+ * @return string
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.ElseExpression)
+ */
+ public function getFSOptionsJson()
+ {
+ $fsOptionItems = null;
+
+ //Special case for gallery/nav which can be the string "thumbs/false/dots"
+ if (is_bool($this->getVar("gallery/fullscreen/nav"))) {
+ $fsOptionItems['nav'] = $this->getVar("gallery/fullscreen/nav") ? 'true' : 'false';
+ } else {
+ $fsOptionItems['nav'] = $this->escapeHtml($this->getVar("gallery/fullscreen/nav"));
+ }
+
+ $fsOptionItems['loop'] = $this->getVar("gallery/fullscreen/loop");
+ $fsOptionItems['navdir'] = $this->escapeHtml($this->getVar("gallery/fullscreen/navdir"));
+ $fsOptionItems['navarrows'] = $this->getVar("gallery/fullscreen/navarrows");
+ $fsOptionItems['navtype'] = $this->escapeHtml($this->getVar("gallery/fullscreen/navtype"));
+ $fsOptionItems['arrows'] = $this->getVar("gallery/fullscreen/arrows");
+ $fsOptionItems['showCaption'] = $this->getVar("gallery/fullscreen/caption");
+
+ if ($this->getVar("gallery/fullscreen/transition/duration")) {
+ $fsOptionItems['transitionduration'] = (int)$this->escapeHtml(
+ $this->getVar("gallery/fullscreen/transition/duration")
+ );
+ }
+
+ $fsOptionItems['transition'] = $this->escapeHtml($this->getVar("gallery/fullscreen/transition/effect"));
+
+ if ($this->getVar("gallery/fullscreen/keyboard")) {
+ $fsOptionItems['keyboard'] = $this->getVar("gallery/fullscreen/keyboard");
+ }
+
+ if ($this->getVar("gallery/fullscreen/thumbmargin")) {
+ $fsOptionItems['thumbmargin'] =
+ (int)$this->escapeHtml($this->getVar("gallery/fullscreen/thumbmargin"));
+ }
+
+ return $this->jsonSerializer->serialize($fsOptionItems);
+ }
+}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options.php b/app/code/Magento/Catalog/Block/Product/View/Options.php
index 0720c018f6a9..c457b20cd090 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options.php
@@ -4,16 +4,15 @@
* See COPYING.txt for license details.
*/
-/**
- * Product options block
- *
- * @author Magento Core Team
- */
namespace Magento\Catalog\Block\Product\View;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Option\Value;
/**
+ * Product options block
+ *
+ * @author Magento Core Team
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
@@ -121,6 +120,8 @@ public function setProduct(Product $product = null)
}
/**
+ * Get group of option.
+ *
* @param string $type
* @return string
*/
@@ -142,6 +143,8 @@ public function getOptions()
}
/**
+ * Check if block has options.
+ *
* @return bool
*/
public function hasOptions()
@@ -160,7 +163,10 @@ public function hasOptions()
*/
protected function _getPriceConfiguration($option)
{
- $optionPrice = $this->pricingHelper->currency($option->getPrice(true), false, false);
+ $optionPrice = $option->getPrice(true);
+ if ($option->getPriceType() !== Value::TYPE_PERCENT) {
+ $optionPrice = $this->pricingHelper->currency($optionPrice, false, false);
+ }
$data = [
'prices' => [
'oldPrice' => [
@@ -195,7 +201,7 @@ protected function _getPriceConfiguration($option)
],
],
'type' => $option->getPriceType(),
- 'name' => $option->getTitle()
+ 'name' => $option->getTitle(),
];
return $data;
}
@@ -231,7 +237,7 @@ public function getJsonConfig()
//pass the return array encapsulated in an object for the other modules to be able to alter it eg: weee
$this->_eventManager->dispatch('catalog_product_option_price_configuration_after', ['configObj' => $configObj]);
- $config=$configObj->getConfig();
+ $config = $configObj->getConfig();
return $this->_jsonEncoder->encode($config);
}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
index 181211a0fc4a..059580b9b5ea 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
@@ -9,11 +9,14 @@
*
* @author Magento Core Team
*/
+
namespace Magento\Catalog\Block\Product\View\Options;
use Magento\Catalog\Pricing\Price\CustomOptionPriceInterface;
/**
+ * Product aoptions section abstract block.
+ *
* @api
* @since 100.0.2
*/
@@ -46,7 +49,7 @@ abstract class AbstractOptions extends \Magento\Framework\View\Element\Template
/**
* @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Framework\Pricing\Helper\Data $pricingHelper
- * @param \Magento\Catalog\Helper\Data $catalogData,
+ * @param \Magento\Catalog\Helper\Data $catalogData
* @param array $data
*/
public function __construct(
@@ -123,6 +126,8 @@ public function getFormattedPrice()
}
/**
+ * Retrieve formatted price.
+ *
* @return string
*
* @deprecated
@@ -134,7 +139,7 @@ public function getFormatedPrice()
}
/**
- * Return formated price
+ * Return formatted price
*
* @param array $value
* @param bool $flag
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
index 7df9b972e150..d9d663b32f4d 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
@@ -3,8 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Block\Product\View\Options\Type;
+use Magento\Catalog\Model\Product\Option;
+use Magento\Catalog\Block\Product\View\Options\Type\Select\CheckableFactory;
+use Magento\Catalog\Block\Product\View\Options\Type\Select\MultipleFactory;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\View\Element\Template\Context;
+use Magento\Framework\Pricing\Helper\Data;
+use Magento\Catalog\Helper\Data as CatalogHelper;
+
/**
* Product options text type block
*
@@ -13,169 +22,60 @@
*/
class Select extends \Magento\Catalog\Block\Product\View\Options\AbstractOptions
{
+ /**
+ * @var CheckableFactory
+ */
+ private $checkableFactory;
+ /**
+ * @var MultipleFactory
+ */
+ private $multipleFactory;
+
+ /**
+ * Select constructor.
+ * @param Context $context
+ * @param Data $pricingHelper
+ * @param CatalogHelper $catalogData
+ * @param array $data
+ * @param CheckableFactory|null $checkableFactory
+ * @param MultipleFactory|null $multipleFactory
+ */
+ public function __construct(
+ Context $context,
+ Data $pricingHelper,
+ CatalogHelper $catalogData,
+ array $data = [],
+ CheckableFactory $checkableFactory = null,
+ MultipleFactory $multipleFactory = null
+ ) {
+ parent::__construct($context, $pricingHelper, $catalogData, $data);
+ $this->checkableFactory = $checkableFactory ?: ObjectManager::getInstance()->get(CheckableFactory::class);
+ $this->multipleFactory = $multipleFactory ?: ObjectManager::getInstance()->get(MultipleFactory::class);
+ }
+
/**
* Return html for control element
*
* @return string
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function getValuesHtml()
{
- $_option = $this->getOption();
- $configValue = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $_option->getId());
- $store = $this->getProduct()->getStore();
-
- $this->setSkipJsReloadPrice(1);
- // Remove inline prototype onclick and onchange events
-
- if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN ||
- $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE
+ $option = $this->getOption();
+ $optionType = $option->getType();
+ if ($optionType === Option::OPTION_TYPE_DROP_DOWN ||
+ $optionType === Option::OPTION_TYPE_MULTIPLE
) {
- $require = $_option->getIsRequire() ? ' required' : '';
- $extraParams = '';
- $select = $this->getLayout()->createBlock(
- \Magento\Framework\View\Element\Html\Select::class
- )->setData(
- [
- 'id' => 'select_' . $_option->getId(),
- 'class' => $require . ' product-custom-option admin__control-select'
- ]
- );
- if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN) {
- $select->setName('options[' . $_option->getId() . ']')->addOption('', __('-- Please Select --'));
- } else {
- $select->setName('options[' . $_option->getId() . '][]');
- $select->setClass('multiselect admin__control-multiselect' . $require . ' product-custom-option');
- }
- foreach ($_option->getValues() as $_value) {
- $priceStr = $this->_formatPrice(
- [
- 'is_percent' => $_value->getPriceType() == 'percent',
- 'pricing_value' => $_value->getPrice($_value->getPriceType() == 'percent'),
- ],
- false
- );
- $select->addOption(
- $_value->getOptionTypeId(),
- $_value->getTitle() . ' ' . strip_tags($priceStr) . '',
- ['price' => $this->pricingHelper->currencyByStore($_value->getPrice(true), $store, false)]
- );
- }
- if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE) {
- $extraParams = ' multiple="multiple"';
- }
- if (!$this->getSkipJsReloadPrice()) {
- $extraParams .= ' onchange="opConfig.reloadPrice()"';
- }
- $extraParams .= ' data-selector="' . $select->getName() . '"';
- $select->setExtraParams($extraParams);
-
- if ($configValue) {
- $select->setValue($configValue);
- }
-
- return $select->getHtml();
+ $optionBlock = $this->multipleFactory->create();
}
-
- if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO ||
- $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX
+ if ($optionType === Option::OPTION_TYPE_RADIO ||
+ $optionType === Option::OPTION_TYPE_CHECKBOX
) {
- $selectHtml = '';
-
- return $selectHtml;
+ $optionBlock = $this->checkableFactory->create();
}
+ return $optionBlock
+ ->setOption($option)
+ ->setProduct($this->getProduct())
+ ->setSkipJsReloadPrice(1)
+ ->_toHtml();
}
}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php
new file mode 100644
index 000000000000..3d856f85dbd9
--- /dev/null
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php
@@ -0,0 +1,68 @@
+ $value->getPriceType() === 'percent',
+ 'pricing_value' => $value->getPrice($value->getPriceType() === 'percent')
+ ]
+ );
+ }
+
+ /**
+ * Returns current currency for store
+ *
+ * @param ProductCustomOptionValuesInterface $value
+ * @return float|string
+ */
+ public function getCurrencyByStore(ProductCustomOptionValuesInterface $value)
+ {
+ /** @noinspection PhpMethodParametersCountMismatchInspection */
+ return $this->pricingHelper->currencyByStore(
+ $value->getPrice(true),
+ $this->getProduct()->getStore(),
+ false
+ );
+ }
+
+ /**
+ * Returns preconfigured value for given option
+ *
+ * @param Option $option
+ * @return string|array|null
+ */
+ public function getPreconfiguredValue(Option $option)
+ {
+ return $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId());
+ }
+}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php
new file mode 100644
index 000000000000..09a931dfa069
--- /dev/null
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php
@@ -0,0 +1,112 @@
+getOption();
+ $optionType = $option->getType();
+ $configValue = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId());
+ $require = $option->getIsRequire() ? ' required' : '';
+ $extraParams = '';
+ /** @var Select $select */
+ $select = $this->getLayout()->createBlock(
+ Select::class
+ )->setData(
+ [
+ 'id' => 'select_' . $option->getId(),
+ 'class' => $require . ' product-custom-option admin__control-select'
+ ]
+ );
+ $select = $this->insertSelectOption($select, $option);
+ $select = $this->processSelectOption($select, $option);
+ if ($optionType === Option::OPTION_TYPE_MULTIPLE) {
+ $extraParams = ' multiple="multiple"';
+ }
+ if (!$this->getSkipJsReloadPrice()) {
+ $extraParams .= ' onchange="opConfig.reloadPrice()"';
+ }
+ $extraParams .= ' data-selector="' . $select->getName() . '"';
+ $select->setExtraParams($extraParams);
+ if ($configValue) {
+ $select->setValue($configValue);
+ }
+ return $select->getHtml();
+ }
+
+ /**
+ * Returns select with inserted option give as a parameter
+ *
+ * @param Select $select
+ * @param Option $option
+ * @return Select
+ */
+ private function insertSelectOption(Select $select, Option $option): Select
+ {
+ $require = $option->getIsRequire() ? ' required' : '';
+ if ($option->getType() === Option::OPTION_TYPE_DROP_DOWN) {
+ $select->setName('options[' . $option->getId() . ']')->addOption('', __('-- Please Select --'));
+ } else {
+ $select->setName('options[' . $option->getId() . '][]');
+ $select->setClass('multiselect admin__control-multiselect' . $require . ' product-custom-option');
+ }
+
+ return $select;
+ }
+
+ /**
+ * Returns select with formated option prices
+ *
+ * @param Select $select
+ * @param Option $option
+ * @return Select
+ */
+ private function processSelectOption(Select $select, Option $option): Select
+ {
+ $store = $this->getProduct()->getStore();
+ foreach ($option->getValues() as $_value) {
+ $isPercentPriceType = $_value->getPriceType() === 'percent';
+ $priceStr = $this->_formatPrice(
+ [
+ 'is_percent' => $isPercentPriceType,
+ 'pricing_value' => $_value->getPrice($isPercentPriceType)
+ ],
+ false
+ );
+ $select->addOption(
+ $_value->getOptionTypeId(),
+ $_value->getTitle() . ' ' . strip_tags($priceStr) . '',
+ [
+ 'price' => $this->pricingHelper->currencyByStore(
+ $_value->getPrice(true),
+ $store,
+ false
+ )
+ ]
+ );
+ }
+
+ return $select;
+ }
+}
diff --git a/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php b/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php
index 704271b58f48..b4c24231a741 100644
--- a/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php
+++ b/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php
@@ -139,7 +139,7 @@ public function getCacheKeyInfo()
[
$this->getDisplayType(),
$this->getProductsPerPage(),
- intval($this->getRequest()->getParam($this->getData('page_var_name'), 1)),
+ (int) $this->getRequest()->getParam($this->getData('page_var_name'), 1),
$this->serializer->serialize($this->getRequest()->getParams())
]
);
diff --git a/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php b/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
index da35b566d7e7..dd2e23e67f3d 100644
--- a/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
+++ b/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
@@ -20,8 +20,8 @@
/**
* Reports Viewed Products Counter
*
- * The main responsilibity of this class is provide necessary data to track viewed products
- * by customer on frontend and data to synchornize this tracks with backend
+ * The main responsibility of this class is provide necessary data to track viewed products
+ * by customer on frontend and data to synchronize this tracks with backend
*
* @api
* @since 101.1.0
@@ -109,6 +109,8 @@ public function __construct(
*
* @return string {JSON encoded data}
* @since 101.1.0
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getCurrentProductData()
{
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php
index 6456b6d578bb..733e270174e4 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Add.php
@@ -6,12 +6,14 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Category;
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
/**
* Class Add Category
*
* @package Magento\Catalog\Controller\Adminhtml\Category
*/
-class Add extends \Magento\Catalog\Controller\Adminhtml\Category
+class Add extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface
{
/**
* Forward factory for result
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/CategoriesJson.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/CategoriesJson.php
index b8865f2de8d1..752257f5b900 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/CategoriesJson.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/CategoriesJson.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Category;
-class CategoriesJson extends \Magento\Catalog\Controller\Adminhtml\Category
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+
+class CategoriesJson extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\Controller\Result\JsonFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php
index 0a54475b15f9..39122d139c90 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Category;
-class Delete extends \Magento\Catalog\Controller\Adminhtml\Category
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+class Delete extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface
{
/**
* @var \Magento\Catalog\Api\CategoryRepositoryInterface
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php
index 6ff478e49a30..0450ff1607a0 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Edit.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Category;
-class Edit extends \Magento\Catalog\Controller\Adminhtml\Category
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Edit extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface
{
/**
* @var \Magento\Framework\Controller\Result\JsonFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php
index 4cc0f2d89d17..d1efa0014d42 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php
@@ -5,12 +5,13 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Category\Image;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\ResultFactory;
/**
* Class Upload
*/
-class Upload extends \Magento\Backend\App\Action
+class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterface
{
/**
* Image uploader
@@ -54,14 +55,6 @@ public function execute()
try {
$result = $this->imageUploader->saveFileToTmpDir($imageId);
-
- $result['cookie'] = [
- 'name' => $this->_getSession()->getName(),
- 'value' => $this->_getSession()->getSessionId(),
- 'lifetime' => $this->_getSession()->getCookieLifetime(),
- 'path' => $this->_getSession()->getCookiePath(),
- 'domain' => $this->_getSession()->getCookieDomain(),
- ];
} catch (\Exception $e) {
$result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php
index 902d71775a3d..a5be6223bee7 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Index.php
@@ -6,7 +6,12 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Category;
-class Index extends \Magento\Catalog\Controller\Adminhtml\Category
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+/**
+ * Controller for category listing
+ */
+class Index extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpGetActionInterface
{
/**
* @var \Magento\Backend\Model\View\Result\ForwardFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php
index df2c80eda141..082101ff0782 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Move.php
@@ -6,7 +6,12 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Category;
-class Move extends \Magento\Catalog\Controller\Adminhtml\Category
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+
+/**
+ * Move category admin controller
+ */
+class Move extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\Controller\Result\JsonFactory
@@ -26,7 +31,7 @@ class Move extends \Magento\Catalog\Controller\Adminhtml\Category
/**
* @param \Magento\Backend\App\Action\Context $context
* @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
- * @param \Magento\Framework\View\LayoutFactory $layoutFactory,
+ * @param \Magento\Framework\View\LayoutFactory $layoutFactory
* @param \Psr\Log\LoggerInterface $logger
*/
public function __construct(
@@ -44,7 +49,7 @@ public function __construct(
/**
* Move category action
*
- * @return \Magento\Framework\Controller\Result\Raw
+ * @return \Magento\Framework\Controller\Result\Json
*/
public function execute()
{
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php
index 9384397b67f9..e3d40bee214d 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/RefreshPath.php
@@ -1,12 +1,16 @@
$categoryId,
'path' => $category->getPath(),
'parentId' => $category->getParentId(),
+ 'level' => $category->getLevel()
]);
}
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php
index aa9ae88754b6..77518fd9bf5c 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Controller\Adminhtml\Category;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Catalog\Api\Data\CategoryAttributeInterface;
use Magento\Store\Model\StoreManagerInterface;
@@ -14,7 +15,7 @@
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Save extends \Magento\Catalog\Controller\Adminhtml\Category
+class Save extends \Magento\Catalog\Controller\Adminhtml\Category implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\Controller\Result\RawFactory
@@ -146,6 +147,7 @@ public function execute()
$parentCategory = $this->getParentCategory($parentId, $storeId);
$category->setPath($parentCategory->getPath());
$category->setParentId($parentCategory->getId());
+ $category->setLevel(null);
}
/**
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Validate.php
index 4eda49068ac3..66a5fb1008a7 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Validate.php
@@ -5,10 +5,14 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Category;
+use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\Catalog\Controller\Adminhtml\Category as CategoryAction;
+
/**
* Catalog category validate
*/
-class Validate extends \Magento\Catalog\Controller\Adminhtml\Category
+class Validate extends CategoryAction implements HttpGetActionInterface, HttpPostActionInterface
{
/**
* @var \Magento\Framework\Controller\Result\JsonFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php
index 7eb391dedf81..3cba09b1e8e9 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Edit.php
@@ -6,10 +6,18 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute;
+use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Ui\Component\MassAction\Filter;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute as AttributeAction;
-class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute
+/**
+ * Form for mass updatings products' attributes.
+ * Can be accessed by GET since it's a form,
+ * can be accessed by POST since it's used as a processor of a mass-action button.
+ */
+class Edit extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface
{
/**
* @var \Magento\Framework\View\Result\PageFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
index 0fbf9054ef1b..342bbc388f87 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
@@ -6,84 +6,80 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute;
+use Magento\AsynchronousOperations\Api\Data\OperationInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Backend\App\Action;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
* Class Save
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute
+class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute implements HttpPostActionInterface
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor
+ * @var \Magento\Framework\Bulk\BulkManagementInterface
*/
- protected $_productFlatIndexerProcessor;
+ private $bulkManagement;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
+ * @var \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory
*/
- protected $_productPriceIndexerProcessor;
+ private $operationFactory;
/**
- * Catalog product
- *
- * @var \Magento\Catalog\Helper\Product
+ * @var \Magento\Framework\DataObject\IdentityGeneratorInterface
*/
- protected $_catalogProduct;
+ private $identityService;
/**
- * @var \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory
+ * @var \Magento\Framework\Serialize\SerializerInterface
*/
- protected $stockItemFactory;
+ private $serializer;
/**
- * Stock Indexer
- *
- * @var \Magento\CatalogInventory\Model\Indexer\Stock\Processor
+ * @var \Magento\Authorization\Model\UserContextInterface
*/
- protected $_stockIndexerProcessor;
+ private $userContext;
/**
- * @var \Magento\Framework\Api\DataObjectHelper
+ * @var int
*/
- protected $dataObjectHelper;
+ private $bulkSize;
/**
* @param Action\Context $context
* @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper
- * @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor
- * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor
- * @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor
- * @param \Magento\Catalog\Helper\Product $catalogProduct
- * @param \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory
- * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+ * @param \Magento\Framework\Bulk\BulkManagementInterface $bulkManagement
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory $operartionFactory
+ * @param \Magento\Framework\DataObject\IdentityGeneratorInterface $identityService
+ * @param \Magento\Framework\Serialize\SerializerInterface $serializer
+ * @param \Magento\Authorization\Model\UserContextInterface $userContext
+ * @param int $bulkSize
*/
public function __construct(
Action\Context $context,
\Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper,
- \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
- \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
- \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor,
- \Magento\Catalog\Helper\Product $catalogProduct,
- \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory,
- \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+ \Magento\Framework\Bulk\BulkManagementInterface $bulkManagement,
+ \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory $operartionFactory,
+ \Magento\Framework\DataObject\IdentityGeneratorInterface $identityService,
+ \Magento\Framework\Serialize\SerializerInterface $serializer,
+ \Magento\Authorization\Model\UserContextInterface $userContext,
+ int $bulkSize = 100
) {
- $this->_productFlatIndexerProcessor = $productFlatIndexerProcessor;
- $this->_productPriceIndexerProcessor = $productPriceIndexerProcessor;
- $this->_stockIndexerProcessor = $stockIndexerProcessor;
- $this->_catalogProduct = $catalogProduct;
- $this->stockItemFactory = $stockItemFactory;
parent::__construct($context, $attributeHelper);
- $this->dataObjectHelper = $dataObjectHelper;
+ $this->bulkManagement = $bulkManagement;
+ $this->operationFactory = $operartionFactory;
+ $this->identityService = $identityService;
+ $this->serializer = $serializer;
+ $this->userContext = $userContext;
+ $this->bulkSize = $bulkSize;
}
/**
* Update product attributes
*
- * @return \Magento\Backend\Model\View\Result\Redirect
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @return \Magento\Framework\Controller\Result\Redirect
*/
public function execute()
{
@@ -92,128 +88,184 @@ public function execute()
}
/* Collect Data */
- $inventoryData = $this->getRequest()->getParam('inventory', []);
$attributesData = $this->getRequest()->getParam('attributes', []);
$websiteRemoveData = $this->getRequest()->getParam('remove_website_ids', []);
$websiteAddData = $this->getRequest()->getParam('add_website_ids', []);
- /* Prepare inventory data item options (use config settings) */
- $options = $this->_objectManager->get(\Magento\CatalogInventory\Api\StockConfigurationInterface::class)
- ->getConfigItemOptions();
- foreach ($options as $option) {
- if (isset($inventoryData[$option]) && !isset($inventoryData['use_config_' . $option])) {
- $inventoryData['use_config_' . $option] = 0;
- }
- }
+ $storeId = $this->attributeHelper->getSelectedStoreId();
+ $websiteId = $this->attributeHelper->getStoreWebsiteId($storeId);
+ $productIds = $this->attributeHelper->getProductIds();
+
+ $attributesData = $this->sanitizeProductAttributes($attributesData);
try {
- $storeId = $this->attributeHelper->getSelectedStoreId();
- if ($attributesData) {
- $dateFormat = $this->_objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class)
- ->getDateFormat(\IntlDateFormatter::SHORT);
-
- foreach ($attributesData as $attributeCode => $value) {
- $attribute = $this->_objectManager->get(\Magento\Eav\Model\Config::class)
- ->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode);
- if (!$attribute->getAttributeId()) {
- unset($attributesData[$attributeCode]);
- continue;
- }
- if ($attribute->getBackendType() == 'datetime') {
- if (!empty($value)) {
- $filterInput = new \Zend_Filter_LocalizedToNormalized(['date_format' => $dateFormat]);
- $filterInternal = new \Zend_Filter_NormalizedToLocalized(
- ['date_format' => \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT]
- );
- $value = $filterInternal->filter($filterInput->filter($value));
- } else {
- $value = null;
- }
- $attributesData[$attributeCode] = $value;
- } elseif ($attribute->getFrontendInput() == 'multiselect') {
- // Check if 'Change' checkbox has been checked by admin for this attribute
- $isChanged = (bool)$this->getRequest()->getPost('toggle_' . $attributeCode);
- if (!$isChanged) {
- unset($attributesData[$attributeCode]);
- continue;
- }
- if (is_array($value)) {
- $value = implode(',', $value);
- }
- $attributesData[$attributeCode] = $value;
- }
- }
+ $this->publish($attributesData, $websiteRemoveData, $websiteAddData, $storeId, $websiteId, $productIds);
+ $this->messageManager->addSuccessMessage(__('Message is added to queue'));
+ } catch (\Magento\Framework\Exception\LocalizedException $e) {
+ $this->messageManager->addErrorMessage($e->getMessage());
+ } catch (\Exception $e) {
+ $this->messageManager->addExceptionMessage(
+ $e,
+ __('Something went wrong while updating the product(s) attributes.')
+ );
+ }
- $this->_objectManager->get(\Magento\Catalog\Model\Product\Action::class)
- ->updateAttributes($this->attributeHelper->getProductIds(), $attributesData, $storeId);
- }
+ return $this->resultRedirectFactory->create()->setPath('catalog/product/', ['store' => $storeId]);
+ }
- if ($inventoryData) {
- // TODO why use ObjectManager?
- /** @var \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry */
- $stockRegistry = $this->_objectManager
- ->create(\Magento\CatalogInventory\Api\StockRegistryInterface::class);
- /** @var \Magento\CatalogInventory\Api\StockItemRepositoryInterface $stockItemRepository */
- $stockItemRepository = $this->_objectManager
- ->create(\Magento\CatalogInventory\Api\StockItemRepositoryInterface::class);
- foreach ($this->attributeHelper->getProductIds() as $productId) {
- $stockItemDo = $stockRegistry->getStockItem(
- $productId,
- $this->attributeHelper->getStoreWebsiteId($storeId)
- );
- if (!$stockItemDo->getProductId()) {
- $inventoryData['product_id'] = $productId;
- }
-
- $stockItemId = $stockItemDo->getId();
- $this->dataObjectHelper->populateWithArray(
- $stockItemDo,
- $inventoryData,
- \Magento\CatalogInventory\Api\Data\StockItemInterface::class
+ /**
+ * Sanitize product attributes
+ *
+ * @param array $attributesData
+ *
+ * @return array
+ */
+ private function sanitizeProductAttributes($attributesData)
+ {
+ $dateFormat = $this->_objectManager->get(TimezoneInterface::class)->getDateFormat(\IntlDateFormatter::SHORT);
+ $config = $this->_objectManager->get(\Magento\Eav\Model\Config::class);
+
+ foreach ($attributesData as $attributeCode => $value) {
+ $attribute = $config->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode);
+ if (!$attribute->getAttributeId()) {
+ unset($attributesData[$attributeCode]);
+ continue;
+ }
+ if ($attribute->getBackendType() === 'datetime') {
+ if (!empty($value)) {
+ $filterInput = new \Zend_Filter_LocalizedToNormalized(['date_format' => $dateFormat]);
+ $filterInternal = new \Zend_Filter_NormalizedToLocalized(
+ ['date_format' => \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT]
);
- $stockItemDo->setItemId($stockItemId);
- $stockItemRepository->save($stockItemDo);
+ $value = $filterInternal->filter($filterInput->filter($value));
+ } else {
+ $value = null;
}
- $this->_stockIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
- }
-
- if ($websiteAddData || $websiteRemoveData) {
- /* @var $actionModel \Magento\Catalog\Model\Product\Action */
- $actionModel = $this->_objectManager->get(\Magento\Catalog\Model\Product\Action::class);
- $productIds = $this->attributeHelper->getProductIds();
-
- if ($websiteRemoveData) {
- $actionModel->updateWebsites($productIds, $websiteRemoveData, 'remove');
+ $attributesData[$attributeCode] = $value;
+ } elseif ($attribute->getFrontendInput() === 'multiselect') {
+ // Check if 'Change' checkbox has been checked by admin for this attribute
+ $isChanged = (bool)$this->getRequest()->getPost('toggle_' . $attributeCode);
+ if (!$isChanged) {
+ unset($attributesData[$attributeCode]);
+ continue;
}
- if ($websiteAddData) {
- $actionModel->updateWebsites($productIds, $websiteAddData, 'add');
+ if (is_array($value)) {
+ $value = implode(',', $value);
}
-
- $this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
+ $attributesData[$attributeCode] = $value;
}
+ }
+ return $attributesData;
+ }
- $this->messageManager->addSuccessMessage(
- __('A total of %1 record(s) were updated.', count($this->attributeHelper->getProductIds()))
- );
-
- $this->_productFlatIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
+ /**
+ * Schedule new bulk
+ *
+ * @param array $attributesData
+ * @param array $websiteRemoveData
+ * @param array $websiteAddData
+ * @param int $storeId
+ * @param int $websiteId
+ * @param array $productIds
+ * @throws \Magento\Framework\Exception\LocalizedException
+ *
+ * @return void
+ */
+ private function publish(
+ $attributesData,
+ $websiteRemoveData,
+ $websiteAddData,
+ $storeId,
+ $websiteId,
+ $productIds
+ ):void {
+ $productIdsChunks = array_chunk($productIds, $this->bulkSize);
+ $bulkUuid = $this->identityService->generateId();
+ $bulkDescription = __('Update attributes for ' . count($productIds) . ' selected products');
+ $operations = [];
+ foreach ($productIdsChunks as $productIdsChunk) {
+ if ($websiteRemoveData || $websiteAddData) {
+ $dataToUpdate = [
+ 'website_assign' => $websiteAddData,
+ 'website_detach' => $websiteRemoveData
+ ];
+ $operations[] = $this->makeOperation(
+ 'Update website assign',
+ 'product_action_attribute.website.update',
+ $dataToUpdate,
+ $storeId,
+ $websiteId,
+ $productIdsChunk,
+ $bulkUuid
+ );
+ }
- if ($this->_catalogProduct->isDataForPriceIndexerWasChanged($attributesData)
- || !empty($websiteRemoveData)
- || !empty($websiteAddData)
- ) {
- $this->_productPriceIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
+ if ($attributesData) {
+ $operations[] = $this->makeOperation(
+ 'Update product attributes',
+ 'product_action_attribute.update',
+ $attributesData,
+ $storeId,
+ $websiteId,
+ $productIdsChunk,
+ $bulkUuid
+ );
}
- } catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addErrorMessage($e->getMessage());
- } catch (\Exception $e) {
- $this->messageManager->addExceptionMessage(
- $e,
- __('Something went wrong while updating the product(s) attributes.')
+ }
+
+ if (!empty($operations)) {
+ $result = $this->bulkManagement->scheduleBulk(
+ $bulkUuid,
+ $operations,
+ $bulkDescription,
+ $this->userContext->getUserId()
);
+ if (!$result) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Something went wrong while processing the request.')
+ );
+ }
}
+ }
+
+ /**
+ * Make asynchronous operation
+ *
+ * @param string $meta
+ * @param string $queue
+ * @param array $dataToUpdate
+ * @param int $storeId
+ * @param int $websiteId
+ * @param array $productIds
+ * @param int $bulkUuid
+ *
+ * @return OperationInterface
+ */
+ private function makeOperation(
+ $meta,
+ $queue,
+ $dataToUpdate,
+ $storeId,
+ $websiteId,
+ $productIds,
+ $bulkUuid
+ ): OperationInterface {
+ $dataToEncode = [
+ 'meta_information' => $meta,
+ 'product_ids' => $productIds,
+ 'store_id' => $storeId,
+ 'website_id' => $websiteId,
+ 'attributes' => $dataToUpdate
+ ];
+ $data = [
+ 'data' => [
+ 'bulk_uuid' => $bulkUuid,
+ 'topic_name' => $queue,
+ 'serialized_data' => $this->serializer->serialize($dataToEncode),
+ 'status' => \Magento\Framework\Bulk\OperationInterface::STATUS_TYPE_OPEN,
+ ]
+ ];
- return $this->resultRedirectFactory->create()
- ->setPath('catalog/product/', ['store' => $this->attributeHelper->getSelectedStoreId()]);
+ return $this->operationFactory->create($data);
}
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php
index a873f08d082d..30a6629dd1c2 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php
@@ -6,7 +6,11 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute;
-class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute
+use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute as AttributeAction;
+
+class Validate extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface
{
/**
* @var \Magento\Framework\Controller\Result\JsonFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php
index bbef1de28e5b..09eacbbf0731 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php
@@ -6,8 +6,10 @@
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Backend\App\Action\Context;
use Magento\Catalog\Api\AttributeSetRepositoryInterface;
use Magento\Catalog\Api\Data\ProductAttributeInterface;
+use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Eav\Api\AttributeGroupRepositoryInterface;
use Magento\Eav\Api\AttributeManagementInterface;
use Magento\Eav\Api\AttributeRepositoryInterface;
@@ -16,8 +18,14 @@
use Magento\Eav\Api\Data\AttributeInterface;
use Magento\Eav\Api\Data\AttributeSetInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\Controller\Result\Json;
+use Magento\Framework\Controller\Result\JsonFactory;
+use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\App\ObjectManager;
use Psr\Log\LoggerInterface;
+use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Api\ExtensionAttributesFactory;
/**
@@ -25,10 +33,10 @@
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Product
+class AddAttributeToTemplate extends Product implements HttpPostActionInterface
{
/**
- * @var \Magento\Framework\Controller\Result\JsonFactory
+ * @var JsonFactory
*/
protected $resultJsonFactory;
@@ -75,33 +83,34 @@ class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Produ
/**
* Constructor
*
- * @param \Magento\Backend\App\Action\Context $context
+ * @param Context $context
* @param Builder $productBuilder
- * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
- * @param \Magento\Eav\Api\Data\AttributeGroupInterfaceFactory|null $attributeGroupFactory
+ * @param JsonFactory $resultJsonFactory
+ * @param AttributeGroupInterfaceFactory|null $attributeGroupFactory
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ * @SuppressWarnings(PHPMD.LongVariable)
*/
public function __construct(
- \Magento\Backend\App\Action\Context $context,
- \Magento\Catalog\Controller\Adminhtml\Product\Builder $productBuilder,
- \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
- \Magento\Eav\Api\Data\AttributeGroupInterfaceFactory $attributeGroupFactory = null
+ Context $context,
+ Builder $productBuilder,
+ JsonFactory $resultJsonFactory,
+ AttributeGroupInterfaceFactory $attributeGroupFactory = null
) {
parent::__construct($context, $productBuilder);
$this->resultJsonFactory = $resultJsonFactory;
- $this->attributeGroupFactory = $attributeGroupFactory ?: \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Eav\Api\Data\AttributeGroupInterfaceFactory::class);
+ $this->attributeGroupFactory = $attributeGroupFactory ?: ObjectManager::getInstance()
+ ->get(AttributeGroupInterfaceFactory::class);
}
/**
* Add attribute to attribute set
*
- * @return \Magento\Framework\Controller\Result\Json
+ * @return Json
*/
public function execute()
{
$request = $this->getRequest();
- $response = new \Magento\Framework\DataObject();
+ $response = new DataObject();
$response->setError(false);
try {
@@ -124,12 +133,12 @@ public function execute()
->getItems();
if (!$attributeGroupItems) {
- throw new \Magento\Framework\Exception\NoSuchEntityException;
+ throw new NoSuchEntityException;
}
/** @var AttributeGroupInterface $attributeGroup */
$attributeGroup = reset($attributeGroupItems);
- } catch (\Magento\Framework\Exception\NoSuchEntityException $e) {
+ } catch (NoSuchEntityException $e) {
/** @var AttributeGroupInterface $attributeGroup */
$attributeGroup = $this->attributeGroupFactory->create();
}
@@ -176,101 +185,114 @@ public function execute()
* Adding basic filters
*
* @return SearchCriteriaBuilder
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
private function getBasicAttributeSearchCriteriaBuilder()
{
- $attributeIds = (array)$this->getRequest()->getParam('attributeIds', []);
+ $attributeIds = (array) $this->getRequest()->getParam('attributeIds', []);
if (empty($attributeIds['selected'])) {
throw new LocalizedException(__('Attributes were missing and must be specified.'));
}
return $this->getSearchCriteriaBuilder()
- ->addFilter('attribute_set_id', new \Zend_Db_Expr('null'), 'is')
->addFilter('attribute_id', [$attributeIds['selected']], 'in');
}
/**
+ * Get AttributeRepositoryInterface
+ *
* @return AttributeRepositoryInterface
*/
private function getAttributeRepository()
{
if (null === $this->attributeRepository) {
- $this->attributeRepository = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Eav\Api\AttributeRepositoryInterface::class);
+ $this->attributeRepository = ObjectManager::getInstance()
+ ->get(AttributeRepositoryInterface::class);
}
return $this->attributeRepository;
}
/**
+ * Get AttributeSetRepositoryInterface
+ *
* @return AttributeSetRepositoryInterface
*/
private function getAttributeSetRepository()
{
if (null === $this->attributeSetRepository) {
- $this->attributeSetRepository = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Catalog\Api\AttributeSetRepositoryInterface::class);
+ $this->attributeSetRepository = ObjectManager::getInstance()
+ ->get(AttributeSetRepositoryInterface::class);
}
return $this->attributeSetRepository;
}
/**
+ * Get AttributeGroupInterface
+ *
* @return AttributeGroupRepositoryInterface
*/
private function getAttributeGroupRepository()
{
if (null === $this->attributeGroupRepository) {
- $this->attributeGroupRepository = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Eav\Api\AttributeGroupRepositoryInterface::class);
+ $this->attributeGroupRepository = ObjectManager::getInstance()
+ ->get(AttributeGroupRepositoryInterface::class);
}
return $this->attributeGroupRepository;
}
/**
+ * Get SearchCriteriaBuilder
+ *
* @return SearchCriteriaBuilder
*/
private function getSearchCriteriaBuilder()
{
if (null === $this->searchCriteriaBuilder) {
- $this->searchCriteriaBuilder = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Framework\Api\SearchCriteriaBuilder::class);
+ $this->searchCriteriaBuilder = ObjectManager::getInstance()
+ ->get(SearchCriteriaBuilder::class);
}
return $this->searchCriteriaBuilder;
}
/**
+ * Get AttributeManagementInterface
+ *
* @return AttributeManagementInterface
*/
private function getAttributeManagement()
{
if (null === $this->attributeManagement) {
- $this->attributeManagement = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Eav\Api\AttributeManagementInterface::class);
+ $this->attributeManagement = ObjectManager::getInstance()
+ ->get(AttributeManagementInterface::class);
}
return $this->attributeManagement;
}
/**
+ * Get LoggerInterface
+ *
* @return LoggerInterface
*/
private function getLogger()
{
if (null === $this->logger) {
- $this->logger = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Psr\Log\LoggerInterface::class);
+ $this->logger = ObjectManager::getInstance()
+ ->get(LoggerInterface::class);
}
return $this->logger;
}
/**
+ * Get ExtensionAttributesFactory.
+ *
* @return ExtensionAttributesFactory
*/
private function getExtensionAttributesFactory()
{
if (null === $this->extensionAttributesFactory) {
- $this->extensionAttributesFactory = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Framework\Api\ExtensionAttributesFactory::class);
+ $this->extensionAttributesFactory = ObjectManager::getInstance()
+ ->get(ExtensionAttributesFactory::class);
}
return $this->extensionAttributesFactory;
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php
index bef6aee0e2af..faa9e4ddf49b 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
-class Delete extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+class Delete extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute implements HttpPostActionInterface
{
/**
* @return \Magento\Backend\Model\View\Result\Redirect
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php
index a99cbdbade18..a41cd71aea46 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
-class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute implements HttpGetActionInterface
{
/**
* @return \Magento\Framework\Controller\ResultInterface
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Index.php
index 9bdc54b289c2..34267121f9b8 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Index.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Index.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
-class Index extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Index extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute implements HttpGetActionInterface
{
/**
* @return \Magento\Backend\Model\View\Result\Page
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/NewAction.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/NewAction.php
index e954d9730591..fdfde7e80609 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/NewAction.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/NewAction.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
-class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute implements HttpGetActionInterface
{
/**
* @var \Magento\Backend\Model\View\Result\ForwardFactory
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 817de6828e48..853cc6527030 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
@@ -7,12 +7,14 @@
namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Backend\App\Action\Context;
use Magento\Backend\Model\View\Result\Redirect;
use Magento\Catalog\Api\Data\ProductAttributeInterface;
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;
@@ -31,9 +33,11 @@
use Magento\Framework\View\Result\PageFactory;
/**
+ * Product attribute save controller.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Save extends Attribute
+class Save extends Attribute implements HttpPostActionInterface
{
/**
* @var BuildFactory
@@ -75,6 +79,11 @@ class Save extends Attribute
*/
private $presentation;
+ /**
+ * @var FormData|null
+ */
+ private $formDataSerializer;
+
/**
* @param Context $context
* @param FrontendInterface $attributeLabelCache
@@ -88,6 +97,7 @@ class Save extends Attribute
* @param Product $productHelper
* @param LayoutFactory $layoutFactory
* @param Presentation|null $presentation
+ * @param FormData|null $formDataSerializer
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -102,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;
@@ -113,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;
@@ -134,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)
@@ -156,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);
@@ -165,30 +195,11 @@ public function execute()
? $model->getAttributeCode()
: $this->getRequest()->getParam('attribute_code');
$attributeCode = $attributeCode ?: $this->generateCode($this->getRequest()->getParam('frontend_label')[0]);
- if (strlen($attributeCode) > 0) {
- $validatorAttrCode = new \Zend_Validate_Regex(
- ['pattern' => '/^[a-z\x{600}-\x{6FF}][a-z\x{600}-\x{6FF}_0-9]{0,30}$/u']
- );
- if (!$validatorAttrCode->isValid($attributeCode)) {
- $this->messageManager->addErrorMessage(
- __(
- 'Attribute code "%1" is invalid. Please use only letters (a-z), ' .
- 'numbers (0-9) or underscore(_) in this field, first character should be a letter.',
- $attributeCode
- )
- );
- return $this->returnResult(
- 'catalog/*/edit',
- ['attribute_id' => $attributeId, '_current' => true],
- ['error' => true]
- );
- }
- }
$data['attribute_code'] = $attributeCode;
//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) {
@@ -229,14 +240,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);
@@ -316,28 +327,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 db452113ada0..c74a382724a0 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
@@ -7,9 +7,20 @@
namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
+use Magento\Catalog\Controller\Adminhtml\Product\Attribute as AttributeAction;
+use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator;
+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\Framework\Serialize\Serializer\FormData;
-class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
+/**
+ * Product attribute validate controller.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class Validate extends AttributeAction implements HttpGetActionInterface, HttpPostActionInterface
{
const DEFAULT_MESSAGE_KEY = 'message';
@@ -28,6 +39,16 @@ class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
*/
private $multipleAttributeList;
+ /**
+ * @var FormData|null
+ */
+ private $formDataSerializer;
+
+ /**
+ * @var AttributeCodeValidator
+ */
+ private $attributeCodeValidator;
+
/**
* Constructor
*
@@ -38,6 +59,8 @@ class Validate extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
* @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
* @param \Magento\Framework\View\LayoutFactory $layoutFactory
* @param array $multipleAttributeList
+ * @param FormData|null $formDataSerializer
+ * @param AttributeCodeValidator|null $attributeCodeValidator
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
@@ -46,15 +69,24 @@ 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,
+ AttributeCodeValidator $attributeCodeValidator = null
) {
parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory);
$this->resultJsonFactory = $resultJsonFactory;
$this->layoutFactory = $layoutFactory;
$this->multipleAttributeList = $multipleAttributeList;
+ $this->formDataSerializer = $formDataSerializer ?: ObjectManager::getInstance()
+ ->get(FormData::class);
+ $this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
+ AttributeCodeValidator::class
+ );
}
/**
+ * @inheritdoc
+ *
* @return \Magento\Framework\Controller\ResultInterface
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -63,6 +95,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');
@@ -75,7 +116,7 @@ public function execute()
$attributeCode
);
- if ($attribute->getId() && !$attributeId) {
+ if ($attribute->getId() && !$attributeId || $attributeCode === 'product_type' || $attributeCode === 'type_id') {
$message = strlen($this->getRequest()->getParam('attribute_code'))
? __('An attribute with this code already exists.')
: __('An attribute with the same code (%1) already exists.', $attributeCode);
@@ -85,6 +126,12 @@ public function execute()
$response->setError(true);
$response->setProductAttribute($attribute->toArray());
}
+
+ if (!$this->attributeCodeValidator->isValid($attributeCode)) {
+ $this->setMessageToResponse($response, $this->attributeCodeValidator->getMessages());
+ $response->setError(true);
+ }
+
if ($this->getRequest()->has('new_attribute_set_name')) {
$setName = $this->getRequest()->getParam('new_attribute_set_name');
/** @var $attributeSet \Magento\Eav\Model\Entity\Attribute\Set */
@@ -102,10 +149,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
@@ -123,7 +170,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
@@ -132,7 +180,7 @@ private function isUniqueAdminValues(array $optionsValues, array $deletedOptions
{
$adminValues = [];
foreach ($optionsValues as $optionKey => $values) {
- if (!(isset($deletedOptions[$optionKey]) and $deletedOptions[$optionKey] === '1')) {
+ if (!(isset($deletedOptions[$optionKey]) && $deletedOptions[$optionKey] === '1')) {
$adminValues[] = reset($values);
}
}
@@ -157,6 +205,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/Builder.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php
index 125406061aed..78ad9f423871 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php
@@ -3,8 +3,11 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\ProductFactory;
use Magento\Cms\Model\Wysiwyg as WysiwygModel;
use Magento\Framework\App\RequestInterface;
@@ -15,6 +18,11 @@
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Type as ProductTypes;
+/**
+ * Build a product based on a request
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class Builder
{
/**
@@ -79,10 +87,11 @@ public function __construct(
* Build product based on user request
*
* @param RequestInterface $request
- * @return \Magento\Catalog\Model\Product
+ * @return ProductInterface
* @throws \RuntimeException
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
- public function build(RequestInterface $request)
+ public function build(RequestInterface $request): ProductInterface
{
$productId = (int) $request->getParam('id');
$storeId = $request->getParam('store', 0);
@@ -92,6 +101,9 @@ public function build(RequestInterface $request)
if ($productId) {
try {
$product = $this->productRepository->getById($productId, true, $storeId);
+ if ($attributeSetId) {
+ $product->setAttributeSetId($attributeSetId);
+ }
} catch (\Exception $e) {
$product = $this->createEmptyProduct(ProductTypes::DEFAULT_TYPE, $attributeSetId, $storeId);
$this->logger->critical($e);
@@ -113,6 +125,8 @@ public function build(RequestInterface $request)
}
/**
+ * Create a product with the given properties
+ *
* @param int $typeId
* @param int $attributeSetId
* @param int $storeId
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Crosssell.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Crosssell.php
index fa94a2fb7be2..e51d3ffe94ae 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Crosssell.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Crosssell.php
@@ -6,7 +6,17 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
-class Crosssell extends \Magento\Catalog\Controller\Adminhtml\Product
+use Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+/**
+ * Class Crosssell
+ *
+ * @package Magento\Catalog\Controller\Adminhtml\Product
+ * @deprecated Not used since cross-sell products grid moved to UI components.
+ * @see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml
+ */
+class Crosssell extends Product implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\View\Result\LayoutFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/CrosssellGrid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/CrosssellGrid.php
index daaf2c21ee2b..5039d0c052b5 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/CrosssellGrid.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/CrosssellGrid.php
@@ -6,7 +6,17 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
-class CrosssellGrid extends \Magento\Catalog\Controller\Adminhtml\Product
+use Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+/**
+ * Class CrosssellGrid
+ *
+ * @package Magento\Catalog\Controller\Adminhtml\Product
+ * @deprecated Not used since cross-sell products grid moved to UI components.
+ * @see Magento_Catalog::view/adminhtml/ui_component/crosssell_product_listing.xml
+ */
+class CrosssellGrid extends Product implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\View\Result\LayoutFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php
index 1b9316a95ad5..c31ceabcda65 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
-class Edit extends \Magento\Catalog\Controller\Adminhtml\Product
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Edit extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpGetActionInterface
{
/**
* Array of actions which can be processed without secret key validation
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php
index b5660ea87934..ff7311e93175 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php
@@ -6,9 +6,10 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Gallery;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
-class Upload extends \Magento\Backend\App\Action
+class Upload extends \Magento\Backend\App\Action implements HttpPostActionInterface
{
/**
* Authorization level of a basic admin session
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/GridOnly.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/GridOnly.php
index 40e62895caff..51aaa8c178ed 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/GridOnly.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/GridOnly.php
@@ -1,12 +1,16 @@
productBuilder->build($this->getRequest());
$block = $this->getRequest()->getParam('gridOnlyBlock');
- $blockClassSuffix = str_replace(' ', '_', ucwords(str_replace('_', ' ', $block)));
+ $blockClassSuffix = ucwords($block, '_');
/** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
$resultRaw = $this->resultRawFactory->create();
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Index.php
index ea66ecf6b162..7755a512eb9b 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Index.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Index.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
-class Index extends \Magento\Catalog\Controller\Adminhtml\Product
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Index extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpGetActionInterface
{
/**
* @var \Magento\Framework\View\Result\PageFactory
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 d82f4a04fb25..f11d16755ef0 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/MassDelete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
index f32c6edd5739..8fceba3c45e2 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
@@ -6,13 +6,14 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\Controller\ResultFactory;
use Magento\Backend\App\Action\Context;
use Magento\Ui\Component\MassAction\Filter;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Catalog\Api\ProductRepositoryInterface;
-class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product
+class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface
{
/**
* Massactions filter
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php
index e3623aabfa1a..9d7273fb3f23 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassStatus.php
@@ -6,6 +6,7 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Backend\App\Action;
use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Framework\Controller\ResultFactory;
@@ -13,9 +14,10 @@
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
+class MassStatus extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface
{
/**
* @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
@@ -86,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/Adminhtml/Product/NewAction.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php
index 0b027105cd7d..0b1ef98c386c 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/NewAction.php
@@ -6,11 +6,12 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
use Magento\Backend\App\Action;
use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Framework\App\ObjectManager;
-class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product
+class NewAction extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpGetActionInterface
{
/**
* @var Initialization\StockDataFilter
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Related.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Related.php
index 4bc15d14a29b..f54f8d469c3e 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Related.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Related.php
@@ -7,7 +7,17 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
-class Related extends \Magento\Catalog\Controller\Adminhtml\Product
+use Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+/**
+ * Class Related
+ *
+ * @package Magento\Catalog\Controller\Adminhtml\Product
+ * @deprecated Not used since related products grid moved to UI components.
+ * @see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml
+ */
+class Related extends Product implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\View\Result\LayoutFactory
@@ -29,6 +39,8 @@ public function __construct(
}
/**
+ * Execute
+ *
* @return \Magento\Framework\View\Result\Layout
*/
public function execute()
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/RelatedGrid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/RelatedGrid.php
index b2fc7ebe1eb3..b1092bba0d36 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/RelatedGrid.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/RelatedGrid.php
@@ -7,6 +7,15 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
-class RelatedGrid extends Related
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+/**
+ * Class RelatedGrid
+ *
+ * @package Magento\Catalog\Controller\Adminhtml\Product
+ * @deprecated Not used since related products grid moved to UI components.
+ * @see Magento_Catalog::view/adminhtml/ui_component/related_product_listing.xml
+ */
+class RelatedGrid extends Related implements HttpPostActionInterface
{
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Reload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Reload.php
index ff87e7f57413..a0963e60d888 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Reload.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Reload.php
@@ -5,12 +5,13 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\Controller\ResultFactory;
/**
* Backend reload of product create/edit form
*/
-class Reload extends \Magento\Catalog\Controller\Adminhtml\Product
+class Reload extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface
{
/**
* {@inheritdoc}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
index ff3ce60d9278..825d0ee032d6 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
@@ -7,7 +7,9 @@
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Backend\App\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\App\Request\DataPersistorInterface;
@@ -16,7 +18,7 @@
* Class Save
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Save extends \Magento\Catalog\Controller\Adminhtml\Product
+class Save extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface
{
/**
* @var Initialization\Helper
@@ -53,6 +55,16 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product
*/
private $storeManager;
+ /**
+ * @var \Magento\Framework\Escaper|null
+ */
+ private $escaper;
+
+ /**
+ * @var null|\Psr\Log\LoggerInterface
+ */
+ private $logger;
+
/**
* Save constructor.
*
@@ -62,6 +74,8 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product
* @param \Magento\Catalog\Model\Product\Copier $productCopier
* @param \Magento\Catalog\Model\Product\TypeTransitionManager $productTypeManager
* @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
+ * @param \Magento\Framework\Escaper|null $escaper
+ * @param \Psr\Log\LoggerInterface|null $logger
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
@@ -69,13 +83,17 @@ public function __construct(
Initialization\Helper $initializationHelper,
\Magento\Catalog\Model\Product\Copier $productCopier,
\Magento\Catalog\Model\Product\TypeTransitionManager $productTypeManager,
- \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
+ \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
+ \Magento\Framework\Escaper $escaper = null,
+ \Psr\Log\LoggerInterface $logger = null
) {
$this->initializationHelper = $initializationHelper;
$this->productCopier = $productCopier;
$this->productTypeManager = $productTypeManager;
$this->productRepository = $productRepository;
parent::__construct($context, $productBuilder);
+ $this->escaper = $escaper ?? $this->_objectManager->get(\Magento\Framework\Escaper::class);
+ $this->logger = $logger ?? $this->_objectManager->get(\Psr\Log\LoggerInterface::class);
}
/**
@@ -102,7 +120,6 @@ public function execute()
$this->productBuilder->build($this->getRequest())
);
$this->productTypeManager->processProduct($product);
-
if (isset($data['product'][$product->getIdFieldName()])) {
throw new \Magento\Framework\Exception\LocalizedException(
__('The product was unable to be saved. Please try again.')
@@ -110,6 +127,7 @@ public function execute()
}
$originalSku = $product->getSku();
+ $canSaveCustomOptions = $product->getCanSaveCustomOptions();
$product->save();
$this->handleImageRemoveError($data, $product->getId());
$this->getCategoryLinkManagement()->assignProductToCategories(
@@ -119,21 +137,17 @@ public function execute()
$productId = $product->getEntityId();
$productAttributeSetId = $product->getAttributeSetId();
$productTypeId = $product->getTypeId();
-
- $this->copyToStores($data, $productId);
-
+ $extendedData = $data;
+ $extendedData['can_save_custom_options'] = $canSaveCustomOptions;
+ $this->copyToStores($extendedData, $productId);
$this->messageManager->addSuccessMessage(__('You saved the product.'));
$this->getDataPersistor()->clear('catalog_product');
if ($product->getSku() != $originalSku) {
$this->messageManager->addNoticeMessage(
__(
'SKU for product %1 has been changed to %2.',
- $this->_objectManager->get(
- \Magento\Framework\Escaper::class
- )->escapeHtml($product->getName()),
- $this->_objectManager->get(
- \Magento\Framework\Escaper::class
- )->escapeHtml($product->getSku())
+ $this->escaper->escapeHtml($product->getName()),
+ $this->escaper->escapeHtml($product->getSku())
)
);
}
@@ -143,17 +157,21 @@ public function execute()
);
if ($redirectBack === 'duplicate') {
+ $product->unsetData('quantity_and_stock_status');
$newProduct = $this->productCopier->copy($product);
+ $this->checkUniqueAttributes($product);
$this->messageManager->addSuccessMessage(__('You duplicated the product.'));
}
} catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
+ $this->logger->critical($e);
$this->messageManager->addExceptionMessage($e);
+ $data = isset($product) ? $this->persistMediaData($product, $data) : $data;
$this->getDataPersistor()->set('catalog_product', $data);
$redirectBack = $productId ? true : 'new';
} catch (\Exception $e) {
- $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
+ $this->logger->critical($e);
$this->messageManager->addErrorMessage($e->getMessage());
+ $data = isset($product) ? $this->persistMediaData($product, $data) : $data;
$this->getDataPersistor()->set('catalog_product', $data);
$redirectBack = $productId ? true : 'new';
}
@@ -186,6 +204,7 @@ public function execute()
/**
* Notify customer when image was not deleted in specific case.
+ *
* TODO: temporary workaround must be eliminated in MAGETWO-45306
*
* @param array $postData
@@ -239,6 +258,7 @@ protected function copyToStores($data, $productId)
->setStoreId($copyFrom)
->load($productId)
->setStoreId($copyTo)
+ ->setCanSaveCustomOptions($data['can_save_custom_options'])
->setCopyFromView(true)
->save();
}
@@ -250,6 +270,8 @@ protected function copyToStores($data, $productId)
}
/**
+ * Get categoryLinkManagement in a backward compatible way.
+ *
* @return \Magento\Catalog\Api\CategoryLinkManagementInterface
*/
private function getCategoryLinkManagement()
@@ -262,6 +284,8 @@ private function getCategoryLinkManagement()
}
/**
+ * Get storeManager in a backward compatible way.
+ *
* @return StoreManagerInterface
* @deprecated 101.0.0
*/
@@ -288,4 +312,57 @@ protected function getDataPersistor()
return $this->dataPersistor;
}
+
+ /**
+ * Persist media gallery on error, in order to show already saved images on next run.
+ *
+ * @param ProductInterface $product
+ * @param array $data
+ * @return array
+ */
+ private function persistMediaData(ProductInterface $product, array $data)
+ {
+ $mediaGallery = $product->getData('media_gallery');
+ if (!empty($mediaGallery['images'])) {
+ foreach ($mediaGallery['images'] as $key => $image) {
+ if (!isset($image['new_file'])) {
+ //Remove duplicates.
+ unset($mediaGallery['images'][$key]);
+ }
+ }
+ $data['product']['media_gallery'] = $mediaGallery;
+ $fields = [
+ 'image',
+ 'small_image',
+ 'thumbnail',
+ 'swatch_image',
+ ];
+ foreach ($fields as $field) {
+ $data['product'][$field] = $product->getData($field);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Check unique attributes and add error to message manager
+ *
+ * @param \Magento\Catalog\Model\Product $product
+ */
+ private function checkUniqueAttributes(\Magento\Catalog\Model\Product $product)
+ {
+ $uniqueLabels = [];
+ foreach ($product->getAttributes() as $attribute) {
+ if ($attribute->getIsUnique() && $attribute->getIsUserDefined()
+ && $product->getData($attribute->getAttributeCode()) !== null
+ ) {
+ $uniqueLabels[] = $attribute->getDefaultFrontendLabel();
+ }
+ }
+ if ($uniqueLabels) {
+ $uniqueLabels = implode('", "', $uniqueLabels);
+ $this->messageManager->addErrorMessage(__('The value of attribute(s) "%1" must be unique', $uniqueLabels));
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Search.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Search.php
index c7c71b2f5602..316983298a1b 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Search.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Search.php
@@ -9,11 +9,12 @@
namespace Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Framework\App\Action\HttpGetActionInterface;
/**
* Controller to search product for ui-select component
*/
-class Search extends \Magento\Backend\App\Action
+class Search extends \Magento\Backend\App\Action implements HttpGetActionInterface
{
/**
* Authorization level of a basic admin session
@@ -48,6 +49,8 @@ public function __construct(
}
/**
+ * Execute product search.
+ *
* @return \Magento\Framework\Controller\ResultInterface
*/
public function execute() : \Magento\Framework\Controller\ResultInterface
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Add.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Add.php
index 480c30322a07..bfe474abba1b 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Add.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Add.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Set;
-class Add extends \Magento\Catalog\Controller\Adminhtml\Product\Set
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Add extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpGetActionInterface
{
/**
* @var \Magento\Framework\View\Result\PageFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php
index f2695311732f..771cc83f79e8 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Set;
-class Delete extends \Magento\Catalog\Controller\Adminhtml\Product\Set
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+class Delete extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpPostActionInterface
{
/**
* @var \Magento\Eav\Api\AttributeSetRepositoryInterface
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Edit.php
index ec540180b034..6f6870cb0849 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Edit.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Edit.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Set;
-class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Set
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Edit extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpGetActionInterface
{
/**
* @var \Magento\Framework\View\Result\PageFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Index.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Index.php
index 29f7dff4f0d4..aadf724f6006 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Index.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Index.php
@@ -6,7 +6,9 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Set;
-class Index extends \Magento\Catalog\Controller\Adminhtml\Product\Set
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
+
+class Index extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpGetActionInterface
{
/**
* @var \Magento\Framework\View\Result\PageFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php
index c5dd9ce6d8e7..83620de25b01 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php
@@ -6,12 +6,13 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Set;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\App\ObjectManager;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Set
+class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Set implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\View\LayoutFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Upsell.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Upsell.php
index 614bddd0ebc8..1cec8e867879 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Upsell.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Upsell.php
@@ -6,7 +6,17 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
-class Upsell extends \Magento\Catalog\Controller\Adminhtml\Product
+use Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+/**
+ * Class Upsell
+ *
+ * @package Magento\Catalog\Controller\Adminhtml\Product
+ * @deprecated Not used since upsell products grid moved to UI components.
+ * @see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml
+ */
+class Upsell extends Product implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\View\Result\LayoutFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/UpsellGrid.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/UpsellGrid.php
index 50beb588b15d..581531e7c93f 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/UpsellGrid.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/UpsellGrid.php
@@ -6,7 +6,17 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
-class UpsellGrid extends \Magento\Catalog\Controller\Adminhtml\Product
+use Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+
+/**
+ * Class UpsellGrid
+ *
+ * @package Magento\Catalog\Controller\Adminhtml\Product
+ * @deprecated Not used since upsell products grid moved to UI components.
+ * @see Magento_Catalog::view/adminhtml/ui_component/upsell_product_listing.xml
+ */
+class UpsellGrid extends Product implements HttpPostActionInterface
{
/**
* @var \Magento\Framework\View\Result\LayoutFactory
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
index e131bfe38c54..77c9cfcd40f0 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
@@ -6,6 +6,8 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Backend\App\Action;
use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Framework\App\ObjectManager;
@@ -16,7 +18,7 @@
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Validate extends \Magento\Catalog\Controller\Adminhtml\Product
+class Validate extends Product implements HttpPostActionInterface, HttpGetActionInterface
{
/**
* @var \Magento\Framework\Stdlib\DateTime\Filter\Date
diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php
index 226e57250507..da3d99a8d274 100644
--- a/app/code/Magento/Catalog/Controller/Category/View.php
+++ b/app/code/Magento/Catalog/Controller/Category/View.php
@@ -7,53 +7,73 @@
namespace Magento\Catalog\Controller\Category;
use Magento\Catalog\Api\CategoryRepositoryInterface;
+use Magento\Catalog\Model\Category;
+use Magento\Catalog\Model\Design;
use Magento\Catalog\Model\Layer\Resolver;
+use Magento\Catalog\Model\Product\ProductList\ToolbarMemorizer;
+use Magento\Catalog\Model\Session;
+use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
+use Magento\Framework\App\Action\Action;
+use Magento\Framework\App\Action\Context;
+use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\App\ActionInterface;
+use Magento\Framework\Controller\Result\ForwardFactory;
+use Magento\Framework\Controller\ResultInterface;
+use Magento\Framework\DataObject;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Registry;
+use Magento\Framework\View\Result\Page;
use Magento\Framework\View\Result\PageFactory;
+use Magento\Store\Model\StoreManagerInterface;
+use Psr\Log\LoggerInterface;
/**
+ * View a category on storefront. Needs to be accessible by POST because of the store switching.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class View extends \Magento\Framework\App\Action\Action
+class View extends Action implements HttpGetActionInterface, HttpPostActionInterface
{
/**
* Core registry
*
- * @var \Magento\Framework\Registry
+ * @var Registry
*/
protected $_coreRegistry = null;
/**
* Catalog session
*
- * @var \Magento\Catalog\Model\Session
+ * @var Session
*/
protected $_catalogSession;
/**
* Catalog design
*
- * @var \Magento\Catalog\Model\Design
+ * @var Design
*/
protected $_catalogDesign;
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var StoreManagerInterface
*/
protected $_storeManager;
/**
- * @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator
+ * @var CategoryUrlPathGenerator
*/
protected $categoryUrlPathGenerator;
/**
- * @var \Magento\Framework\View\Result\PageFactory
+ * @var PageFactory
*/
protected $resultPageFactory;
/**
- * @var \Magento\Framework\Controller\Result\ForwardFactory
+ * @var ForwardFactory
*/
protected $resultForwardFactory;
@@ -69,32 +89,39 @@ class View extends \Magento\Framework\App\Action\Action
*/
protected $categoryRepository;
+ /**
+ * @var ToolbarMemorizer
+ */
+ private $toolbarMemorizer;
+
/**
* Constructor
*
- * @param \Magento\Framework\App\Action\Context $context
- * @param \Magento\Catalog\Model\Design $catalogDesign
- * @param \Magento\Catalog\Model\Session $catalogSession
- * @param \Magento\Framework\Registry $coreRegistry
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator
- * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
- * @param \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory
+ * @param Context $context
+ * @param Design $catalogDesign
+ * @param Session $catalogSession
+ * @param Registry $coreRegistry
+ * @param StoreManagerInterface $storeManager
+ * @param CategoryUrlPathGenerator $categoryUrlPathGenerator
+ * @param PageFactory $resultPageFactory
+ * @param ForwardFactory $resultForwardFactory
* @param Resolver $layerResolver
* @param CategoryRepositoryInterface $categoryRepository
+ * @param ToolbarMemorizer|null $toolbarMemorizer
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Framework\App\Action\Context $context,
- \Magento\Catalog\Model\Design $catalogDesign,
- \Magento\Catalog\Model\Session $catalogSession,
- \Magento\Framework\Registry $coreRegistry,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator,
+ Context $context,
+ Design $catalogDesign,
+ Session $catalogSession,
+ Registry $coreRegistry,
+ StoreManagerInterface $storeManager,
+ CategoryUrlPathGenerator $categoryUrlPathGenerator,
PageFactory $resultPageFactory,
- \Magento\Framework\Controller\Result\ForwardFactory $resultForwardFactory,
+ ForwardFactory $resultForwardFactory,
Resolver $layerResolver,
- CategoryRepositoryInterface $categoryRepository
+ CategoryRepositoryInterface $categoryRepository,
+ ToolbarMemorizer $toolbarMemorizer = null
) {
parent::__construct($context);
$this->_storeManager = $storeManager;
@@ -106,12 +133,13 @@ public function __construct(
$this->resultForwardFactory = $resultForwardFactory;
$this->layerResolver = $layerResolver;
$this->categoryRepository = $categoryRepository;
+ $this->toolbarMemorizer = $toolbarMemorizer ?: $context->getObjectManager()->get(ToolbarMemorizer::class);
}
/**
* Initialize requested category object
*
- * @return \Magento\Catalog\Model\Category|bool
+ * @return Category|bool
*/
protected function _initCategory()
{
@@ -130,13 +158,14 @@ 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',
['category' => $category, 'controller_action' => $this]
);
- } catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
+ } catch (LocalizedException $e) {
+ $this->_objectManager->get(LoggerInterface::class)->critical($e);
return false;
}
@@ -146,13 +175,12 @@ protected function _initCategory()
/**
* Category view action
*
- * @return \Magento\Framework\Controller\ResultInterface
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @return ResultInterface
+ * @throws NoSuchEntityException
*/
public function execute()
{
- if ($this->_request->getParam(\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED)) {
+ if ($this->_request->getParam(ActionInterface::PARAM_NAME_URL_ENCODED)) {
return $this->resultRedirectFactory->create()->setUrl($this->_redirect->getRedirectUrl());
}
$category = $this->_initCategory();
@@ -173,29 +201,18 @@ public function execute()
$page->getConfig()->setPageLayout($settings->getPageLayout());
}
- $hasChildren = $category->hasChildren();
- if ($category->getIsAnchor()) {
- $type = $hasChildren ? 'layered' : 'layered_without_children';
- } else {
- $type = $hasChildren ? 'default' : 'default_without_children';
- }
+ $pageType = $this->getPageType($category);
- if (!$hasChildren) {
+ if (!$category->hasChildren()) {
// Two levels removed from parent. Need to add default page type.
- $parentType = strtok($type, '_');
- $page->addPageLayoutHandles(['type' => $parentType], null, false);
+ $parentPageType = strtok($pageType, '_');
+ $page->addPageLayoutHandles(['type' => $parentPageType], null, false);
}
- $page->addPageLayoutHandles(['type' => $type], null, false);
+ $page->addPageLayoutHandles(['type' => $pageType], null, false);
$page->addPageLayoutHandles(['id' => $category->getId()]);
// apply custom layout update once layout is loaded
- $layoutUpdates = $settings->getLayoutUpdates();
- if ($layoutUpdates && is_array($layoutUpdates)) {
- foreach ($layoutUpdates as $layoutUpdate) {
- $page->addUpdate($layoutUpdate);
- $page->addPageLayoutHandles(['layout_update' => md5($layoutUpdate)], null, false);
- }
- }
+ $this->applyLayoutUpdates($page, $settings);
$page->getConfig()->addBodyClass('page-products')
->addBodyClass('categorypath-' . $this->categoryUrlPathGenerator->getUrlPath($category))
@@ -206,4 +223,40 @@ public function execute()
return $this->resultForwardFactory->create()->forward('noroute');
}
}
+
+ /**
+ * Get page type based on category
+ *
+ * @param Category $category
+ * @return string
+ */
+ private function getPageType(Category $category) : string
+ {
+ $hasChildren = $category->hasChildren();
+ if ($category->getIsAnchor()) {
+ return $hasChildren ? 'layered' : 'layered_without_children';
+ }
+
+ return $hasChildren ? 'default' : 'default_without_children';
+ }
+
+ /**
+ * Apply custom layout updates
+ *
+ * @param Page $page
+ * @param DataObject $settings
+ * @return void
+ */
+ private function applyLayoutUpdates(
+ Page $page,
+ DataObject $settings
+ ) {
+ $layoutUpdates = $settings->getLayoutUpdates();
+ if ($layoutUpdates && is_array($layoutUpdates)) {
+ foreach ($layoutUpdates as $layoutUpdate) {
+ $page->addUpdate($layoutUpdate);
+ $page->addPageLayoutHandles(['layout_update' => sha1($layoutUpdate)], null, false);
+ }
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Controller/Index/Index.php b/app/code/Magento/Catalog/Controller/Index/Index.php
index eae3325df9fc..bd00c9720499 100644
--- a/app/code/Magento/Catalog/Controller/Index/Index.php
+++ b/app/code/Magento/Catalog/Controller/Index/Index.php
@@ -5,12 +5,17 @@
*/
namespace Magento\Catalog\Controller\Index;
-class Index extends \Magento\Framework\App\Action\Action
+use Magento\Framework\App\Action\HttpGetActionInterface;
+
+/**
+ * Catalog index page controller.
+ */
+class Index extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface
{
/**
* Index action
*
- * @return $this
+ * @return \Magento\Framework\Controller\Result\Redirect
*/
public function execute()
{
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare.php b/app/code/Magento/Catalog/Controller/Product/Compare.php
index 1ee146e5aaa7..084a82f87d64 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Controller\Product;
use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\Data\Form\FormKey\Validator;
use Magento\Framework\View\Result\PageFactory;
@@ -15,7 +16,7 @@
* @SuppressWarnings(PHPMD.LongVariable)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-abstract class Compare extends \Magento\Framework\App\Action\Action
+abstract class Compare extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface
{
/**
* Customer id
@@ -139,4 +140,15 @@ public function setCustomerId($customerId)
$this->_customerId = $customerId;
return $this;
}
+
+ /**
+ * @inheritdoc
+ */
+ public function execute()
+ {
+ $resultRedirect = $this->resultRedirectFactory->create();
+ $resultRedirect->setPath('catalog/product_compare');
+
+ return $resultRedirect;
+ }
}
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
index eb9cc8312554..d99901c915a1 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
@@ -6,9 +6,10 @@
*/
namespace Magento\Catalog\Controller\Product\Compare;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\Exception\NoSuchEntityException;
-class Add extends \Magento\Catalog\Controller\Product\Compare
+class Add extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface
{
/**
* Add item to compare list
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php b/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php
index 568fbf1d0567..2703e9869bd4 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php
@@ -6,9 +6,10 @@
*/
namespace Magento\Catalog\Controller\Product\Compare;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\Controller\ResultFactory;
-class Clear extends \Magento\Catalog\Controller\Product\Compare
+class Clear extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface
{
/**
* Remove all items from comparison list
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Index.php b/app/code/Magento/Catalog/Controller/Product/Compare/Index.php
index 3eba058318a7..c0aa32a56ed1 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Index.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Index.php
@@ -6,6 +6,7 @@
*/
namespace Magento\Catalog\Controller\Product\Compare;
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Data\Form\FormKey\Validator;
use Magento\Framework\View\Result\PageFactory;
@@ -13,7 +14,7 @@
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Index extends \Magento\Catalog\Controller\Product\Compare
+class Index extends \Magento\Catalog\Controller\Product\Compare implements HttpGetActionInterface
{
/**
* @var \Magento\Framework\Url\DecoderInterface
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
index 2acbe5ce4d58..eac0ddf94af2 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
@@ -6,9 +6,10 @@
*/
namespace Magento\Catalog\Controller\Product\Compare;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\Exception\NoSuchEntityException;
-class Remove extends \Magento\Catalog\Controller\Product\Compare
+class Remove extends \Magento\Catalog\Controller\Product\Compare implements HttpPostActionInterface
{
/**
* Remove item from compare list
diff --git a/app/code/Magento/Catalog/Controller/Product/View.php b/app/code/Magento/Catalog/Controller/Product/View.php
index ed437361fddd..024123e15150 100644
--- a/app/code/Magento/Catalog/Controller/Product/View.php
+++ b/app/code/Magento/Catalog/Controller/Product/View.php
@@ -6,10 +6,16 @@
*/
namespace Magento\Catalog\Controller\Product;
+use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
use Magento\Framework\App\Action\Context;
use Magento\Framework\View\Result\PageFactory;
+use Magento\Catalog\Controller\Product as ProductAction;
-class View extends \Magento\Catalog\Controller\Product
+/**
+ * View a product on storefront. Needs to be accessible by POST because of the store switching.
+ */
+class View extends ProductAction implements HttpGetActionInterface, HttpPostActionInterface
{
/**
* @var \Magento\Catalog\Helper\Product\View
diff --git a/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php b/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php
index 7cc3eb9e3d2d..25f6d0c32368 100644
--- a/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php
+++ b/app/code/Magento/Catalog/Cron/DeleteOutdatedPriceValues.php
@@ -12,6 +12,8 @@
use Magento\Store\Model\Store;
/**
+ * Cron job for removing outdated prices.
+ *
* Cron operation is responsible for deleting all product prices on WEBSITE level
* in case 'Catalog Price Scope' configuration parameter is set to GLOBAL.
*/
@@ -76,7 +78,7 @@ public function execute()
/**
* Checks if price scope config option explicitly equal to global value.
*
- * Such strict comparision is required to prevent price deleting when
+ * Such strict comparison is required to prevent price deleting when
* price scope config option is null for some reason.
*
* @return bool
diff --git a/app/code/Magento/Catalog/Cron/FrontendActionsFlush.php b/app/code/Magento/Catalog/Cron/FrontendActionsFlush.php
index 6e7699abb477..99e9898eab3c 100644
--- a/app/code/Magento/Catalog/Cron/FrontendActionsFlush.php
+++ b/app/code/Magento/Catalog/Cron/FrontendActionsFlush.php
@@ -57,8 +57,7 @@ private function getLifeTimeByNamespace($namespace)
];
}
- return isset($configuration['lifetime']) ?
- (int) $configuration['lifetime'] : FrontendStorageConfigurationInterface::DEFAULT_LIFETIME;
+ return (int)$configuration['lifetime'] ?? FrontendStorageConfigurationInterface::DEFAULT_LIFETIME;
}
/**
diff --git a/app/code/Magento/Catalog/Helper/Data.php b/app/code/Magento/Catalog/Helper/Data.php
index ae20cda46079..3e9676363283 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 4f128d639b2b..9b8d0ad75a8c 100644
--- a/app/code/Magento/Catalog/Helper/Image.php
+++ b/app/code/Magento/Catalog/Helper/Image.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Helper;
use Magento\Framework\App\Helper\AbstractHelper;
+use Magento\Framework\View\Element\Block\ArgumentInterface;
/**
* Catalog image helper
@@ -14,7 +15,7 @@
* @SuppressWarnings(PHPMD.TooManyFields)
* @since 100.0.2
*/
-class Image extends AbstractHelper
+class Image extends AbstractHelper implements ArgumentInterface
{
/**
* Media config node
@@ -298,6 +299,7 @@ public function resize($width, $height = null)
*
* @param int $quality
* @return $this
+ * @deprecated
*/
public function setQuality($quality)
{
@@ -406,7 +408,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 +536,8 @@ public function getUrl()
}
/**
+ * Save changes
+ *
* @return $this
*/
public function save()
@@ -553,6 +558,8 @@ public function getResizedImageInfo()
}
/**
+ * Getter for placeholder url
+ *
* @param null|string $placeholder
* @return string
*/
@@ -655,7 +662,8 @@ protected function getWatermarkPosition()
/**
* Set watermark size
- * param size in format 100x200
+ *
+ * Param size in format 100x200
*
* @param string $size
* @return $this
@@ -757,7 +765,7 @@ protected function getImageFile()
protected function parseSize($string)
{
$size = explode('x', strtolower($string));
- if (sizeof($size) == 2) {
+ if (count($size) == 2) {
return ['width' => $size[0] > 0 ? $size[0] : null, 'height' => $size[1] > 0 ? $size[1] : null];
}
return false;
@@ -859,7 +867,7 @@ public function getFrame()
*/
protected function getAttribute($name)
{
- return isset($this->attributes[$name]) ? $this->attributes[$name] : null;
+ return $this->attributes[$name] ?? null;
}
/**
diff --git a/app/code/Magento/Catalog/Helper/Output.php b/app/code/Magento/Catalog/Helper/Output.php
index ad0f9508cb67..33e261dc353b 100644
--- a/app/code/Magento/Catalog/Helper/Output.php
+++ b/app/code/Magento/Catalog/Helper/Output.php
@@ -116,7 +116,7 @@ public function addHandler($method, $handler)
public function getHandlers($method)
{
$method = strtolower($method);
- return isset($this->_handlers[$method]) ? $this->_handlers[$method] : [];
+ return $this->_handlers[$method] ?? [];
}
/**
diff --git a/app/code/Magento/Catalog/Helper/Product/Compare.php b/app/code/Magento/Catalog/Helper/Product/Compare.php
index 90d98874e00c..d6d35c5c76dd 100644
--- a/app/code/Magento/Catalog/Helper/Product/Compare.php
+++ b/app/code/Magento/Catalog/Helper/Product/Compare.php
@@ -166,7 +166,15 @@ public function getListUrl()
*/
public function getPostDataParams($product)
{
- return $this->postHelper->getPostData($this->getAddUrl(), ['product' => $product->getId()]);
+ $params = ['product' => $product->getId()];
+ $requestingPageUrl = $this->_getRequest()->getParam('requesting_page_url');
+
+ if (!empty($requestingPageUrl)) {
+ $encodedUrl = $this->urlEncoder->encode($requestingPageUrl);
+ $params[\Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED] = $encodedUrl;
+ }
+
+ return $this->postHelper->getPostData($this->getAddUrl(), $params);
}
/**
diff --git a/app/code/Magento/Catalog/Helper/Product/Configuration.php b/app/code/Magento/Catalog/Helper/Product/Configuration.php
index 9b47e2990099..5b8f6fad6e18 100644
--- a/app/code/Magento/Catalog/Helper/Product/Configuration.php
+++ b/app/code/Magento/Catalog/Helper/Product/Configuration.php
@@ -55,6 +55,7 @@ class Configuration extends AbstractHelper implements ConfigurationInterface
* @param \Magento\Framework\Filter\FilterManager $filter
* @param \Magento\Framework\Stdlib\StringUtils $string
* @param Json $serializer
+ * @param Escaper $escaper
*/
public function __construct(
\Magento\Framework\App\Helper\Context $context,
diff --git a/app/code/Magento/Catalog/Helper/Product/ProductList.php b/app/code/Magento/Catalog/Helper/Product/ProductList.php
index fbea73a6324d..3aa6aeed3779 100644
--- a/app/code/Magento/Catalog/Helper/Product/ProductList.php
+++ b/app/code/Magento/Catalog/Helper/Product/ProductList.php
@@ -42,6 +42,7 @@ class ProductList
/**
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
+ * @param \Magento\Framework\Registry $coreRegistry
*/
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
diff --git a/app/code/Magento/Catalog/Helper/Product/View.php b/app/code/Magento/Catalog/Helper/Product/View.php
index 5753910c125d..74f40a18971d 100644
--- a/app/code/Magento/Catalog/Helper/Product/View.php
+++ b/app/code/Magento/Catalog/Helper/Product/View.php
@@ -10,7 +10,9 @@
/**
* Catalog category helper
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class View extends \Magento\Framework\App\Helper\AbstractHelper
{
@@ -105,19 +107,16 @@ public function __construct(
*
* @param \Magento\Framework\View\Result\Page $resultPage
* @param \Magento\Catalog\Model\Product $product
- * @return \Magento\Framework\View\Result\Page
+ * @return $this
*/
private function preparePageMetadata(ResultPage $resultPage, $product)
{
$pageLayout = $resultPage->getLayout();
$pageConfig = $resultPage->getConfig();
- $title = $product->getMetaTitle();
- if ($title) {
- $pageConfig->getTitle()->set($title);
- } else {
- $pageConfig->getTitle()->set($product->getName());
- }
+ $metaTitle = $product->getMetaTitle();
+ $pageConfig->setMetaTitle($metaTitle);
+ $pageConfig->getTitle()->set($metaTitle ?: $product->getName());
$keyword = $product->getMetaKeyword();
$currentCategory = $this->_coreRegistry->registry('current_category');
diff --git a/app/code/Magento/Catalog/Model/AbstractModel.php b/app/code/Magento/Catalog/Model/AbstractModel.php
index 007635b12433..78a49cd1e8b1 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/ConditionBuilder/EavAttributeCondition.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
index d3c84e69c954..e296c8d3b897 100644
--- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
+++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
@@ -58,22 +58,38 @@ public function build(Filter $filter): string
$conditionValue = $this->mapConditionValue($conditionType, $filter->getValue());
// NOTE: store scope was ignored intentionally to perform search across all stores
- $attributeSelect = $this->resourceConnection->getConnection()
- ->select()
- ->from(
- [$tableAlias => $attribute->getBackendTable()],
- $tableAlias . '.' . $attribute->getEntityIdField()
- )->where(
- $this->resourceConnection->getConnection()->prepareSqlCondition(
- $tableAlias . '.' . $attribute->getIdFieldName(),
- ['eq' => $attribute->getAttributeId()]
- )
- )->where(
- $this->resourceConnection->getConnection()->prepareSqlCondition(
- $tableAlias . '.value',
- [$conditionType => $conditionValue]
- )
- );
+ if ($conditionType == 'is_null') {
+ $entityResourceModel = $attribute->getEntity();
+ $attributeSelect = $this->resourceConnection->getConnection()
+ ->select()
+ ->from(
+ [Collection::MAIN_TABLE_ALIAS => $entityResourceModel->getEntityTable()],
+ Collection::MAIN_TABLE_ALIAS . '.' . $entityResourceModel->getEntityIdField()
+ )->joinLeft(
+ [$tableAlias => $attribute->getBackendTable()],
+ $tableAlias . '.' . $attribute->getEntityIdField() . '=' . Collection::MAIN_TABLE_ALIAS .
+ '.' . $entityResourceModel->getEntityIdField() . ' AND ' . $tableAlias . '.' .
+ $attribute->getIdFieldName() . '=' . $attribute->getAttributeId(),
+ ''
+ )->where($tableAlias . '.value is null');
+ } else {
+ $attributeSelect = $this->resourceConnection->getConnection()
+ ->select()
+ ->from(
+ [$tableAlias => $attribute->getBackendTable()],
+ $tableAlias . '.' . $attribute->getEntityIdField()
+ )->where(
+ $this->resourceConnection->getConnection()->prepareSqlCondition(
+ $tableAlias . '.' . $attribute->getIdFieldName(),
+ ['eq' => $attribute->getAttributeId()]
+ )
+ )->where(
+ $this->resourceConnection->getConnection()->prepareSqlCondition(
+ $tableAlias . '.value',
+ [$conditionType => $conditionValue]
+ )
+ );
+ }
return $this->resourceConnection
->getConnection()
@@ -86,6 +102,8 @@ public function build(Filter $filter): string
}
/**
+ * Get attribute entity by its code
+ *
* @param string $field
* @return Attribute
* @throws \Magento\Framework\Exception\LocalizedException
diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/NativeAttributeCondition.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/NativeAttributeCondition.php
index d072acf4c719..71b9a9c47037 100644
--- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/NativeAttributeCondition.php
+++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/NativeAttributeCondition.php
@@ -77,7 +77,7 @@ private function mapConditionType(string $conditionType, string $field): string
];
}
- return isset($conditionsMap[$conditionType]) ? $conditionsMap[$conditionType] : $conditionType;
+ return $conditionsMap[$conditionType] ?? $conditionType;
}
/**
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 f70bab73d083..66a9132ae44b 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/Attribute/Backend/Consumer.php b/app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php
new file mode 100644
index 000000000000..dc24a3090481
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php
@@ -0,0 +1,163 @@
+catalogProduct = $catalogProduct;
+ $this->productFlatIndexerProcessor = $productFlatIndexerProcessor;
+ $this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
+ $this->productAction = $action;
+ $this->logger = $logger;
+ $this->serializer = $serializer;
+ $this->operationManagement = $operationManagement;
+ $this->entityManager = $entityManager;
+ }
+
+ /**
+ * Process
+ *
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
+ * @throws \Exception
+ *
+ * @return void
+ */
+ public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
+ {
+ try {
+ $serializedData = $operation->getSerializedData();
+ $data = $this->serializer->unserialize($serializedData);
+ $this->execute($data);
+ } catch (\Zend_Db_Adapter_Exception $e) {
+ $this->logger->critical($e->getMessage());
+ if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
+ || $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
+ || $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
+ ) {
+ $status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } else {
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __(
+ 'Sorry, something went wrong during product attributes update. Please see log for details.'
+ );
+ }
+ } catch (NoSuchEntityException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = ($e instanceof TemporaryStateExceptionInterface)
+ ? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
+ : OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (LocalizedException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (\Exception $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
+ }
+
+ $operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
+ ->setErrorCode($errorCode ?? null)
+ ->setResultMessage($message ?? null);
+
+ $this->entityManager->save($operation);
+ }
+
+ /**
+ * Execute
+ *
+ * @param array $data
+ *
+ * @return void
+ */
+ private function execute($data): void
+ {
+ $this->productAction->updateAttributes($data['product_ids'], $data['attributes'], $data['store_id']);
+ if ($this->catalogProduct->isDataForPriceIndexerWasChanged($data['attributes'])) {
+ $this->productPriceIndexerProcessor->reindexList($data['product_ids']);
+ }
+
+ $this->productFlatIndexerProcessor->reindexList($data['product_ids']);
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php b/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php
new file mode 100644
index 000000000000..32ba39d9afd9
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php
@@ -0,0 +1,168 @@
+productFlatIndexerProcessor = $productFlatIndexerProcessor;
+ $this->productAction = $action;
+ $this->logger = $logger;
+ $this->serializer = $serializer;
+ $this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
+ $this->entityManager = $entityManager;
+ }
+
+ /**
+ * Process
+ *
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
+ * @throws \Exception
+ *
+ * @return void
+ */
+ public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
+ {
+ try {
+ $serializedData = $operation->getSerializedData();
+ $data = $this->serializer->unserialize($serializedData);
+ $this->execute($data);
+ } catch (\Zend_Db_Adapter_Exception $e) {
+ $this->logger->critical($e->getMessage());
+ if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
+ || $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
+ || $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
+ ) {
+ $status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __($e->getMessage());
+ } else {
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __(
+ 'Sorry, something went wrong during product attributes update. Please see log for details.'
+ );
+ }
+ } catch (NoSuchEntityException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = ($e instanceof TemporaryStateExceptionInterface)
+ ? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
+ : OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (LocalizedException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (\Exception $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
+ }
+
+ $operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
+ ->setErrorCode($errorCode ?? null)
+ ->setResultMessage($message ?? null);
+
+ $this->entityManager->save($operation);
+ }
+
+ /**
+ * Update website in products
+ *
+ * @param array $productIds
+ * @param array $websiteRemoveData
+ * @param array $websiteAddData
+ *
+ * @return void
+ */
+ private function updateWebsiteInProducts($productIds, $websiteRemoveData, $websiteAddData): void
+ {
+ if ($websiteRemoveData) {
+ $this->productAction->updateWebsites($productIds, $websiteRemoveData, 'remove');
+ }
+ if ($websiteAddData) {
+ $this->productAction->updateWebsites($productIds, $websiteAddData, 'add');
+ }
+ }
+
+ /**
+ * Execute
+ *
+ * @param array $data
+ *
+ * @return void
+ */
+ private function execute($data): void
+ {
+ $this->updateWebsiteInProducts(
+ $data['product_ids'],
+ $data['attributes']['website_detach'],
+ $data['attributes']['website_assign']
+ );
+ $this->productPriceIndexerProcessor->reindexList($data['product_ids']);
+ $this->productFlatIndexerProcessor->reindexList($data['product_ids']);
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php
index cf79ff01d315..d911bec0aaac 100644
--- a/app/code/Magento/Catalog/Model/Category.php
+++ b/app/code/Magento/Catalog/Model/Category.php
@@ -300,7 +300,7 @@ protected function _construct()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function getCustomAttributesCodes()
{
@@ -312,6 +312,8 @@ protected function getCustomAttributesCodes()
}
/**
+ * Returns model resource
+ *
* @throws \Magento\Framework\Exception\LocalizedException
* @return \Magento\Catalog\Model\ResourceModel\Category
* @deprecated because resource models should be used directly
@@ -562,7 +564,7 @@ public function getStoreIds()
*
* If store id is underfined for category return current active store id
*
- * @return integer
+ * @return int
*/
public function getStoreId()
{
@@ -648,6 +650,8 @@ public function formatUrlKey($str)
}
/**
+ * Returns image url
+ *
* @param string $attributeCode
* @return bool|string
* @throws \Magento\Framework\Exception\LocalizedException
@@ -708,7 +712,7 @@ public function getParentId()
return $parentId;
}
$parentIds = $this->getParentIds();
- return intval(array_pop($parentIds));
+ return (int) array_pop($parentIds);
}
/**
@@ -796,6 +800,7 @@ public function getChildren($recursive = false, $isActive = true, $sortByPositio
/**
* Retrieve Stores where isset category Path
+ *
* Return comma separated string
*
* @return string
@@ -826,6 +831,7 @@ public function checkId($id)
/**
* Get array categories ids which are part of category path
+ *
* Result array contain id of current category because it is part of the path
*
* @return array
@@ -1029,7 +1035,8 @@ public function getAvailableSortBy()
/**
* Retrieve Available Product Listing Sort By
- * code as key, value - name
+ *
+ * Code as key, value - name
*
* @return array
*/
@@ -1112,10 +1119,15 @@ public function reindex()
}
}
$productIndexer = $this->indexerRegistry->get(Indexer\Category\Product::INDEXER_ID);
- if (!$productIndexer->isScheduled()
- && (!empty($this->getAffectedProductIds()) || $this->dataHasChangedFor('is_anchor'))
- ) {
- $productIndexer->reindexList($this->getPathIds());
+
+ if (!empty($this->getAffectedProductIds())
+ || $this->dataHasChangedFor('is_anchor')
+ || $this->dataHasChangedFor('is_active')) {
+ if (!$productIndexer->isScheduled()) {
+ $productIndexer->reindexList($this->getPathIds());
+ } else {
+ $productIndexer->invalidate();
+ }
}
}
@@ -1140,16 +1152,27 @@ public function getIdentities()
$identities = [
self::CACHE_TAG . '_' . $this->getId(),
];
- if (!$this->getId() || $this->hasDataChanges()
- || $this->isDeleted() || $this->dataHasChangedFor(self::KEY_INCLUDE_IN_MENU)
- ) {
+
+ if ($this->hasDataChanges()) {
+ $identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $this->getId();
+ }
+
+ if ($this->dataHasChangedFor('is_anchor') || $this->dataHasChangedFor('is_active')) {
+ foreach ($this->getPathIds() as $id) {
+ $identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $id;
+ }
+ }
+
+ if (!$this->getId() || $this->isDeleted() || $this->dataHasChangedFor(self::KEY_INCLUDE_IN_MENU)) {
$identities[] = self::CACHE_TAG;
$identities[] = Product::CACHE_PRODUCT_CATEGORY_TAG . '_' . $this->getId();
}
- return $identities;
+ return array_unique($identities);
}
/**
+ * Returns path
+ *
* @codeCoverageIgnoreStart
* @return string|null
*/
@@ -1159,6 +1182,8 @@ public function getPath()
}
/**
+ * Returns position
+ *
* @return int|null
*/
public function getPosition()
@@ -1167,6 +1192,8 @@ public function getPosition()
}
/**
+ * Returns children count
+ *
* @return int
*/
public function getChildrenCount()
@@ -1175,6 +1202,8 @@ public function getChildrenCount()
}
/**
+ * Returns created at
+ *
* @return string|null
*/
public function getCreatedAt()
@@ -1183,6 +1212,8 @@ public function getCreatedAt()
}
/**
+ * Returns updated at
+ *
* @return string|null
*/
public function getUpdatedAt()
@@ -1191,6 +1222,8 @@ public function getUpdatedAt()
}
/**
+ * Returns is active
+ *
* @return bool
* @SuppressWarnings(PHPMD.BooleanGetMethodName)
*/
@@ -1200,6 +1233,8 @@ public function getIsActive()
}
/**
+ * Returns category id
+ *
* @return int|null
*/
public function getCategoryId()
@@ -1208,6 +1243,8 @@ public function getCategoryId()
}
/**
+ * Returns display mode
+ *
* @return string|null
*/
public function getDisplayMode()
@@ -1216,6 +1253,8 @@ public function getDisplayMode()
}
/**
+ * Returns is include in menu
+ *
* @return bool|null
*/
public function getIncludeInMenu()
@@ -1224,6 +1263,8 @@ public function getIncludeInMenu()
}
/**
+ * Returns url key
+ *
* @return string|null
*/
public function getUrlKey()
@@ -1232,6 +1273,8 @@ public function getUrlKey()
}
/**
+ * Returns children data
+ *
* @return \Magento\Catalog\Api\Data\CategoryTreeInterface[]|null
*/
public function getChildrenData()
@@ -1347,6 +1390,8 @@ public function setLevel($level)
}
/**
+ * Set updated at
+ *
* @param string $updatedAt
* @return $this
*/
@@ -1356,6 +1401,8 @@ public function setUpdatedAt($updatedAt)
}
/**
+ * Set created at
+ *
* @param string $createdAt
* @return $this
*/
@@ -1365,6 +1412,8 @@ public function setCreatedAt($createdAt)
}
/**
+ * Set path
+ *
* @param string $path
* @return $this
*/
@@ -1374,6 +1423,8 @@ public function setPath($path)
}
/**
+ * Set available sort by
+ *
* @param string[]|string $availableSortBy
* @return $this
*/
@@ -1383,6 +1434,8 @@ public function setAvailableSortBy($availableSortBy)
}
/**
+ * Set include in menu
+ *
* @param bool $includeInMenu
* @return $this
*/
@@ -1403,6 +1456,8 @@ public function setProductCount($productCount)
}
/**
+ * Set children data
+ *
* @param \Magento\Catalog\Api\Data\CategoryTreeInterface[] $childrenData
* @return $this
*/
@@ -1412,7 +1467,7 @@ public function setChildrenData(array $childrenData = null)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @return \Magento\Catalog\Api\Data\CategoryExtensionInterface|null
*/
@@ -1422,7 +1477,7 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @param \Magento\Catalog\Api\Data\CategoryExtensionInterface $extensionAttributes
* @return $this
diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Source/Layout.php b/app/code/Magento/Catalog/Model/Category/Attribute/Source/Layout.php
index 1890ea0f7d99..20ea899a3d0d 100644
--- a/app/code/Magento/Catalog/Model/Category/Attribute/Source/Layout.php
+++ b/app/code/Magento/Catalog/Model/Category/Attribute/Source/Layout.php
@@ -17,6 +17,12 @@ class Layout extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource
*/
protected $pageLayoutBuilder;
+ /**
+ * @inheritdoc
+ * @deprecated since the cache is now handled by \Magento\Theme\Model\PageLayout\Config\Builder::$configFiles
+ */
+ protected $_options = null;
+
/**
* @param \Magento\Framework\View\Model\PageLayout\Config\BuilderInterface $pageLayoutBuilder
*/
@@ -26,14 +32,14 @@ public function __construct(\Magento\Framework\View\Model\PageLayout\Config\Buil
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAllOptions()
{
- if (!$this->_options) {
- $this->_options = $this->pageLayoutBuilder->getPageLayoutsConfig()->toOptionArray();
- array_unshift($this->_options, ['value' => '', 'label' => __('No layout updates')]);
- }
- return $this->_options;
+ $options = $this->pageLayoutBuilder->getPageLayoutsConfig()->toOptionArray();
+ array_unshift($options, ['value' => '', 'label' => __('No layout updates')]);
+ $this->_options = $options;
+
+ return $options;
}
}
diff --git a/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php b/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php
index f22c6903a230..4ea06d4e34d7 100644
--- a/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php
+++ b/app/code/Magento/Catalog/Model/Category/Link/SaveHandler.php
@@ -6,7 +6,6 @@
namespace Magento\Catalog\Model\Category\Link;
use Magento\Catalog\Api\Data\CategoryLinkInterface;
-use Magento\Catalog\Model\Indexer\Product\Category;
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
/**
@@ -40,6 +39,8 @@ public function __construct(
}
/**
+ * Execute
+ *
* @param object $entity
* @param array $arguments
* @return object
@@ -78,6 +79,8 @@ public function execute($entity, $arguments = [])
}
/**
+ * Get category links positions
+ *
* @param object $entity
* @return array
*/
@@ -106,27 +109,19 @@ private function getCategoryLinksPositions($entity)
*/
private function mergeCategoryLinks($newCategoryPositions, $oldCategoryPositions)
{
- $result = [];
if (empty($newCategoryPositions)) {
- return $result;
+ return [];
}
+ $categoryPositions = array_combine(array_column($oldCategoryPositions, 'category_id'), $oldCategoryPositions);
foreach ($newCategoryPositions as $newCategoryPosition) {
- $key = array_search(
- $newCategoryPosition['category_id'],
- array_column($oldCategoryPositions, 'category_id')
- );
-
- if ($key === false) {
- $result[] = $newCategoryPosition;
- } elseif (isset($oldCategoryPositions[$key])
- && $oldCategoryPositions[$key]['position'] != $newCategoryPosition['position']
- ) {
- $result[] = $newCategoryPositions[$key];
- unset($oldCategoryPositions[$key]);
+ $categoryId = $newCategoryPosition['category_id'];
+ if (!isset($categoryPositions[$categoryId])) {
+ $categoryPositions[$categoryId] = ['category_id' => $categoryId];
}
+ $categoryPositions[$categoryId]['position'] = $newCategoryPosition['position'];
}
- $result = array_merge($result, $oldCategoryPositions);
+ $result = array_values($categoryPositions);
return $result;
}
diff --git a/app/code/Magento/Catalog/Model/Category/Product/PositionResolver.php b/app/code/Magento/Catalog/Model/Category/Product/PositionResolver.php
index 1e07c0cdd924..44bf153f8369 100644
--- a/app/code/Magento/Catalog/Model/Category/Product/PositionResolver.php
+++ b/app/code/Magento/Catalog/Model/Category/Product/PositionResolver.php
@@ -43,6 +43,8 @@ public function getPositions(int $categoryId): array
$categoryId
)->order(
'ccp.position ' . \Magento\Framework\DB\Select::SQL_ASC
+ )->order(
+ 'ccp.product_id ' . \Magento\Framework\DB\Select::SQL_DESC
);
return array_flip($connection->fetchCol($select));
diff --git a/app/code/Magento/Catalog/Model/Category/Tree.php b/app/code/Magento/Catalog/Model/Category/Tree.php
index 6080f74d5fa0..0a9cb25d7b0e 100644
--- a/app/code/Magento/Catalog/Model/Category/Tree.php
+++ b/app/code/Magento/Catalog/Model/Category/Tree.php
@@ -32,27 +32,40 @@ class Tree
*/
protected $treeFactory;
+ /**
+ * @var \Magento\Catalog\Model\ResourceModel\Category\TreeFactory
+ */
+ private $treeResourceFactory;
+
/**
* @param \Magento\Catalog\Model\ResourceModel\Category\Tree $categoryTree
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection
* @param \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory $treeFactory
+ * @param \Magento\Catalog\Model\ResourceModel\Category\TreeFactory|null $treeResourceFactory
*/
public function __construct(
\Magento\Catalog\Model\ResourceModel\Category\Tree $categoryTree,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection,
- \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory $treeFactory
+ \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory $treeFactory,
+ \Magento\Catalog\Model\ResourceModel\Category\TreeFactory $treeResourceFactory = null
) {
$this->categoryTree = $categoryTree;
$this->storeManager = $storeManager;
$this->categoryCollection = $categoryCollection;
$this->treeFactory = $treeFactory;
+ $this->treeResourceFactory = $treeResourceFactory ?? \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\Catalog\Model\ResourceModel\Category\TreeFactory::class);
}
/**
+ * Get root node by category.
+ *
* @param \Magento\Catalog\Model\Category|null $category
* @return Node|null
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getRootNode($category = null)
{
@@ -71,13 +84,18 @@ public function getRootNode($category = null)
}
/**
+ * Get node by category.
+ *
* @param \Magento\Catalog\Model\Category $category
* @return Node
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function getNode(\Magento\Catalog\Model\Category $category)
{
$nodeId = $category->getId();
- $node = $this->categoryTree->loadNode($nodeId);
+ $categoryTree = $this->treeResourceFactory->create();
+ $node = $categoryTree->loadNode($nodeId);
$node->loadChildren();
$this->prepareCollection();
$this->categoryTree->addCollectionData($this->categoryCollection);
@@ -85,7 +103,11 @@ protected function getNode(\Magento\Catalog\Model\Category $category)
}
/**
+ * Prepare category collection.
+ *
* @return void
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function prepareCollection()
{
@@ -104,6 +126,8 @@ protected function prepareCollection()
}
/**
+ * Get tree by node.
+ *
* @param \Magento\Framework\Data\Tree\Node $node
* @param int $depth
* @param int $currentLevel
@@ -127,6 +151,8 @@ public function getTree($node, $depth = null, $currentLevel = 0)
}
/**
+ * Get node children.
+ *
* @param \Magento\Framework\Data\Tree\Node $node
* @param int $depth
* @param int $currentLevel
diff --git a/app/code/Magento/Catalog/Model/CategoryList.php b/app/code/Magento/Catalog/Model/CategoryList.php
index 790ea6b921fb..cab8e013d9ba 100644
--- a/app/code/Magento/Catalog/Model/CategoryList.php
+++ b/app/code/Magento/Catalog/Model/CategoryList.php
@@ -15,6 +15,9 @@
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
+/**
+ * Class for getting category list.
+ */
class CategoryList implements CategoryListInterface
{
/**
@@ -64,7 +67,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList(SearchCriteriaInterface $searchCriteria)
{
diff --git a/app/code/Magento/Catalog/Model/Config.php b/app/code/Magento/Catalog/Model/Config.php
index d2ffd6f44004..5dce940308a4 100644
--- a/app/code/Magento/Catalog/Model/Config.php
+++ b/app/code/Magento/Catalog/Model/Config.php
@@ -381,7 +381,7 @@ public function getProductTypeName($id)
$this->loadProductTypes();
- return isset($this->_productTypesById[$id]) ? $this->_productTypesById[$id] : false;
+ return $this->_productTypesById[$id] ?? false;
}
/**
diff --git a/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php b/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php
index e2b0a9157402..10675a7b7c7e 100644
--- a/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php
+++ b/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Catalog\Model\Config\CatalogClone\Media;
+use Magento\Framework\Escaper;
+use Magento\Framework\App\ObjectManager;
+
/**
* Clone model for media images related config fields
*
@@ -26,6 +29,11 @@ class Image extends \Magento\Framework\App\Config\Value
*/
protected $_attributeCollectionFactory;
+ /**
+ * @var Escaper
+ */
+ private $escaper;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -36,6 +44,9 @@ class Image extends \Magento\Framework\App\Config\Value
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
+ * @param Escaper|null $escaper
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -46,8 +57,10 @@ public function __construct(
\Magento\Eav\Model\Config $eavConfig,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ Escaper $escaper = null
) {
+ $this->escaper = $escaper ?? ObjectManager::getInstance()->get(Escaper::class);
$this->_attributeCollectionFactory = $attributeCollectionFactory;
$this->_eavConfig = $eavConfig;
parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
@@ -71,10 +84,9 @@ public function getPrefixes()
$prefixes = [];
foreach ($collection as $attribute) {
- /* @var $attribute \Magento\Eav\Model\Entity\Attribute */
$prefixes[] = [
'field' => $attribute->getAttributeCode() . '_',
- 'label' => $attribute->getFrontend()->getLabel(),
+ 'label' => $this->escaper->escapeHtml($attribute->getFrontend()->getLabel()),
];
}
diff --git a/app/code/Magento/Catalog/Model/Design.php b/app/code/Magento/Catalog/Model/Design.php
index bd7cdabb4085..853bbeac8eb3 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 b83bb97301b9..497ed2fd4995 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/ImageExtractor.php b/app/code/Magento/Catalog/Model/ImageExtractor.php
index 1c2060867067..1cb1f305a220 100644
--- a/app/code/Magento/Catalog/Model/ImageExtractor.php
+++ b/app/code/Magento/Catalog/Model/ImageExtractor.php
@@ -9,6 +9,9 @@
use Magento\Catalog\Model\Product\Attribute\Backend\Media\ImageEntryConverter;
use Magento\Framework\View\Xsd\Media\TypeDataExtractorInterface;
+/**
+ * Image extractor from xml configuration
+ */
class ImageExtractor implements TypeDataExtractorInterface
{
/**
@@ -17,6 +20,7 @@ class ImageExtractor implements TypeDataExtractorInterface
* @param \DOMElement $mediaNode
* @param string $mediaParentTag
* @return array
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function process(\DOMElement $mediaNode, $mediaParentTag)
{
@@ -36,7 +40,13 @@ public function process(\DOMElement $mediaNode, $mediaParentTag)
if ($attributeTagName === 'background') {
$nodeValue = $this->processImageBackground($attribute->nodeValue);
} elseif ($attributeTagName === 'width' || $attributeTagName === 'height') {
- $nodeValue = intval($attribute->nodeValue);
+ $nodeValue = (int) $attribute->nodeValue;
+ } elseif ($attributeTagName === 'constrain'
+ || $attributeTagName === 'aspect_ratio'
+ || $attributeTagName === 'frame'
+ || $attributeTagName === 'transparency'
+ ) {
+ $nodeValue = in_array($attribute->nodeValue, [true, 1, 'true', '1'], true) ?? false;
} else {
$nodeValue = $attribute->nodeValue;
}
diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php
index ce92a2c1d958..b5ca0895d6d1 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/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php
index 4b16a4810c0a..1506ccf6963b 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php
@@ -7,8 +7,10 @@
namespace Magento\Catalog\Model\Indexer\Category\Flat;
use Magento\Framework\App\ResourceConnection;
-use Magento\Framework\EntityManager\MetadataPool;
+/**
+ * Abstract action class for category flat indexers.
+ */
class AbstractAction
{
/**
@@ -111,7 +113,7 @@ public function getColumns()
public function getMainStoreTable($storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID)
{
if (is_string($storeId)) {
- $storeId = intval($storeId);
+ $storeId = (int) $storeId;
}
$suffix = sprintf('store_%d', $storeId);
@@ -131,7 +133,7 @@ protected function getFlatTableStructure($tableName)
$table = $this->connection->newTable(
$tableName
)->setComment(
- sprintf("Catalog Category Flat", $tableName)
+ 'Catalog Category Flat'
);
//Adding columns
@@ -379,7 +381,7 @@ protected function getAttributeValues($entityIds, $storeId)
$linkField = $this->getCategoryMetadata()->getLinkField();
foreach ($attributesType as $type) {
foreach ($this->getAttributeTypeValues($type, $entityIds, $storeId) as $row) {
- if (isset($row[$linkField]) && isset($row['attribute_id'])) {
+ if (isset($row[$linkField], $row['attribute_id'])) {
$attributeId = $row['attribute_id'];
if (isset($attributes[$attributeId])) {
$attributeCode = $attributes[$attributeId]['attribute_code'];
@@ -497,6 +499,8 @@ protected function getTableName($name)
}
/**
+ * Get category metadata instance.
+ *
* @return \Magento\Framework\EntityManager\EntityMetadata
*/
private function getCategoryMetadata()
@@ -510,6 +514,8 @@ private function getCategoryMetadata()
}
/**
+ * Get skip static columns instance.
+ *
* @return array
*/
private function getSkipStaticColumns()
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 64a8f930d83e..a62e3d8f83b8 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 6a499883ac72..178f4172ce6f 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/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
index f8121b55dbf9..eb59acb56c35 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
@@ -3,33 +3,46 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Indexer\Category\Product\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Config;
+use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Query\Generator as QueryGenerator;
use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Select;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Indexer\BatchProviderInterface;
+use Magento\Framework\Indexer\BatchSizeManagementInterface;
use Magento\Indexer\Model\ProcessManager;
+use Magento\Store\Model\Store;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class Full reindex action
*
- * @package Magento\Catalog\Model\Indexer\Category\Product\Action
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction
+class Full extends AbstractAction
{
/**
- * @var \Magento\Framework\Indexer\BatchSizeManagementInterface
+ * @var BatchSizeManagementInterface
*/
private $batchSizeManagement;
/**
- * @var \Magento\Framework\Indexer\BatchProviderInterface
+ * @var BatchProviderInterface
*/
private $batchProvider;
/**
- * @var \Magento\Framework\EntityManager\MetadataPool
+ * @var MetadataPool
*/
protected $metadataPool;
@@ -52,25 +65,25 @@ class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
/**
* @param ResourceConnection $resource
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Catalog\Model\Config $config
+ * @param StoreManagerInterface $storeManager
+ * @param Config $config
* @param QueryGenerator|null $queryGenerator
- * @param \Magento\Framework\Indexer\BatchSizeManagementInterface|null $batchSizeManagement
- * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
- * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool
+ * @param BatchSizeManagementInterface|null $batchSizeManagement
+ * @param BatchProviderInterface|null $batchProvider
+ * @param MetadataPool|null $metadataPool
* @param int|null $batchRowsCount
* @param ActiveTableSwitcher|null $activeTableSwitcher
* @param ProcessManager $processManager
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Framework\App\ResourceConnection $resource,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Catalog\Model\Config $config,
+ ResourceConnection $resource,
+ StoreManagerInterface $storeManager,
+ Config $config,
QueryGenerator $queryGenerator = null,
- \Magento\Framework\Indexer\BatchSizeManagementInterface $batchSizeManagement = null,
- \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
+ BatchSizeManagementInterface $batchSizeManagement = null,
+ BatchProviderInterface $batchProvider = null,
+ MetadataPool $metadataPool = null,
$batchRowsCount = null,
ActiveTableSwitcher $activeTableSwitcher = null,
ProcessManager $processManager = null
@@ -81,15 +94,15 @@ public function __construct(
$config,
$queryGenerator
);
- $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
+ $objectManager = ObjectManager::getInstance();
$this->batchSizeManagement = $batchSizeManagement ?: $objectManager->get(
- \Magento\Framework\Indexer\BatchSizeManagementInterface::class
+ BatchSizeManagementInterface::class
);
$this->batchProvider = $batchProvider ?: $objectManager->get(
- \Magento\Framework\Indexer\BatchProviderInterface::class
+ BatchProviderInterface::class
);
$this->metadataPool = $metadataPool ?: $objectManager->get(
- \Magento\Framework\EntityManager\MetadataPool::class
+ MetadataPool::class
);
$this->batchRowsCount = $batchRowsCount;
$this->activeTableSwitcher = $activeTableSwitcher ?: $objectManager->get(ActiveTableSwitcher::class);
@@ -97,33 +110,39 @@ public function __construct(
}
/**
+ * Create the store tables
+ *
* @return void
*/
- private function createTables()
+ private function createTables(): void
{
foreach ($this->storeManager->getStores() as $store) {
- $this->tableMaintainer->createTablesForStore($store->getId());
+ $this->tableMaintainer->createTablesForStore((int)$store->getId());
}
}
/**
+ * Truncates the replica tables
+ *
* @return void
*/
- private function clearReplicaTables()
+ private function clearReplicaTables(): void
{
foreach ($this->storeManager->getStores() as $store) {
- $this->connection->truncateTable($this->tableMaintainer->getMainReplicaTable($store->getId()));
+ $this->connection->truncateTable($this->tableMaintainer->getMainReplicaTable((int)$store->getId()));
}
}
/**
+ * Switches the active table
+ *
* @return void
*/
- private function switchTables()
+ private function switchTables(): void
{
$tablesToSwitch = [];
foreach ($this->storeManager->getStores() as $store) {
- $tablesToSwitch[] = $this->tableMaintainer->getMainTable($store->getId());
+ $tablesToSwitch[] = $this->tableMaintainer->getMainTable((int)$store->getId());
}
$this->activeTableSwitcher->switchTable($this->connection, $tablesToSwitch);
}
@@ -133,12 +152,13 @@ private function switchTables()
*
* @return $this
*/
- public function execute()
+ public function execute(): self
{
$this->createTables();
$this->clearReplicaTables();
$this->reindex();
$this->switchTables();
+
return $this;
}
@@ -147,7 +167,7 @@ public function execute()
*
* @return void
*/
- protected function reindex()
+ protected function reindex(): void
{
$userFunctions = [];
@@ -165,9 +185,9 @@ protected function reindex()
/**
* Execute indexation by store
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
*/
- private function reindexStore($store)
+ private function reindexStore($store): void
{
$this->reindexRootCategory($store);
$this->reindexAnchorCategories($store);
@@ -177,31 +197,31 @@ private function reindexStore($store)
/**
* Publish data from tmp to replica table
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return void
*/
- private function publishData($store)
+ private function publishData($store): void
{
- $select = $this->connection->select()->from($this->tableMaintainer->getMainTmpTable($store->getId()));
+ $select = $this->connection->select()->from($this->tableMaintainer->getMainTmpTable((int)$store->getId()));
$columns = array_keys(
- $this->connection->describeTable($this->tableMaintainer->getMainReplicaTable($store->getId()))
+ $this->connection->describeTable($this->tableMaintainer->getMainReplicaTable((int)$store->getId()))
);
- $tableName = $this->tableMaintainer->getMainReplicaTable($store->getId());
+ $tableName = $this->tableMaintainer->getMainReplicaTable((int)$store->getId());
$this->connection->query(
$this->connection->insertFromSelect(
$select,
$tableName,
$columns,
- \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::INSERT_ON_DUPLICATE
)
);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
- protected function reindexRootCategory(\Magento\Store\Model\Store $store)
+ protected function reindexRootCategory(Store $store): void
{
if ($this->isIndexRootCategoryNeeded()) {
$this->reindexCategoriesBySelect($this->getAllProducts($store), 'cp.entity_id IN (?)', $store);
@@ -211,10 +231,10 @@ protected function reindexRootCategory(\Magento\Store\Model\Store $store)
/**
* Reindex products of anchor categories
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return void
*/
- protected function reindexAnchorCategories(\Magento\Store\Model\Store $store)
+ protected function reindexAnchorCategories(Store $store): void
{
$this->reindexCategoriesBySelect($this->getAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store);
}
@@ -222,10 +242,10 @@ protected function reindexAnchorCategories(\Magento\Store\Model\Store $store)
/**
* Reindex products of non anchor categories
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return void
*/
- protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store)
+ protected function reindexNonAnchorCategories(Store $store): void
{
$this->reindexCategoriesBySelect($this->getNonAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store);
}
@@ -233,40 +253,42 @@ protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store)
/**
* Reindex categories using given SQL select and condition.
*
- * @param \Magento\Framework\DB\Select $basicSelect
+ * @param Select $basicSelect
* @param string $whereCondition
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return void
*/
- private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSelect, $whereCondition, $store)
+ private function reindexCategoriesBySelect(Select $basicSelect, $whereCondition, $store): void
{
- $this->tableMaintainer->createMainTmpTable($store->getId());
+ $this->tableMaintainer->createMainTmpTable((int)$store->getId());
- $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
+ $entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
$columns = array_keys(
- $this->connection->describeTable($this->tableMaintainer->getMainTmpTable($store->getId()))
+ $this->connection->describeTable($this->tableMaintainer->getMainTmpTable((int)$store->getId()))
);
$this->batchSizeManagement->ensureBatchSize($this->connection, $this->batchRowsCount);
- $batches = $this->batchProvider->getBatches(
- $this->connection,
- $entityMetadata->getEntityTable(),
+
+ $select = $this->connection->select();
+ $select->distinct(true);
+ $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+
+ $batchQueries = $this->prepareSelectsByRange(
+ $select,
$entityMetadata->getIdentifierField(),
- $this->batchRowsCount
+ (int)$this->batchRowsCount
);
- foreach ($batches as $batch) {
- $this->connection->delete($this->tableMaintainer->getMainTmpTable($store->getId()));
+
+ foreach ($batchQueries as $query) {
+ $this->connection->delete($this->tableMaintainer->getMainTmpTable((int)$store->getId()));
+ $entityIds = $this->connection->fetchCol($query);
$resultSelect = clone $basicSelect;
- $select = $this->connection->select();
- $select->distinct(true);
- $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
- $entityIds = $this->batchProvider->getBatchIds($this->connection, $select, $batch);
$resultSelect->where($whereCondition, $entityIds);
$this->connection->query(
$this->connection->insertFromSelect(
$resultSelect,
- $this->tableMaintainer->getMainTmpTable($store->getId()),
+ $this->tableMaintainer->getMainTmpTable((int)$store->getId()),
$columns,
- \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::INSERT_ON_DUPLICATE
)
);
$this->publishData($store);
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
index a218266c2503..cb708695255d 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
@@ -17,6 +17,8 @@
use Magento\Store\Model\StoreManagerInterface;
/**
+ * Category rows indexer.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Rows extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction
@@ -213,6 +215,7 @@ protected function isRangingNeeded()
/**
* Returns a list of category ids which are assigned to product ids in the index
*
+ * @param array $productIds
* @return \Magento\Framework\Indexer\CacheContext
*/
private function getCategoryIdsFromIndex(array $productIds)
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
index 802176092d14..ed8f692885d9 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
@@ -7,26 +7,41 @@
namespace Magento\Catalog\Model\Indexer\Product\Eav\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Query\BatchIteratorInterface;
+use Magento\Framework\DB\Query\Generator as QueryGenerator;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Indexer\BatchProviderInterface;
+use Magento\Store\Model\ScopeInterface;
/**
* Class Full reindex action
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction
+class Full extends AbstractAction
{
/**
- * @var \Magento\Framework\EntityManager\MetadataPool
+ * @var MetadataPool
*/
private $metadataPool;
/**
- * @var \Magento\Framework\Indexer\BatchProviderInterface
+ * @var BatchProviderInterface
*/
private $batchProvider;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator
+ * @var BatchSizeCalculator
*/
private $batchSizeCalculator;
@@ -36,44 +51,54 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction
private $activeTableSwitcher;
/**
- * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ * @var ScopeConfigInterface
*/
private $scopeConfig;
/**
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory
- * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool
- * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator
+ * @var QueryGenerator|null
+ */
+ private $batchQueryGenerator;
+
+ /**
+ * @param DecimalFactory $eavDecimalFactory
+ * @param SourceFactory $eavSourceFactory
+ * @param MetadataPool|null $metadataPool
+ * @param BatchProviderInterface|null $batchProvider
+ * @param BatchSizeCalculator $batchSizeCalculator
* @param ActiveTableSwitcher|null $activeTableSwitcher
- * @param \Magento\Framework\App\Config\ScopeConfigInterface|null $scopeConfig
+ * @param ScopeConfigInterface|null $scopeConfig
+ * @param QueryGenerator|null $batchQueryGenerator
*/
public function __construct(
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
- \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator = null,
+ DecimalFactory $eavDecimalFactory,
+ SourceFactory $eavSourceFactory,
+ MetadataPool $metadataPool = null,
+ BatchProviderInterface $batchProvider = null,
+ BatchSizeCalculator $batchSizeCalculator = null,
ActiveTableSwitcher $activeTableSwitcher = null,
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null
+ ScopeConfigInterface $scopeConfig = null,
+ QueryGenerator $batchQueryGenerator = null
) {
- $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- \Magento\Framework\App\Config\ScopeConfigInterface::class
+ $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(
+ ScopeConfigInterface::class
);
parent::__construct($eavDecimalFactory, $eavSourceFactory, $scopeConfig);
- $this->metadataPool = $metadataPool ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- \Magento\Framework\EntityManager\MetadataPool::class
+ $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(
+ MetadataPool::class
);
- $this->batchProvider = $batchProvider ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- \Magento\Framework\Indexer\BatchProviderInterface::class
+ $this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get(
+ BatchProviderInterface::class
);
- $this->batchSizeCalculator = $batchSizeCalculator ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator::class
+ $this->batchSizeCalculator = $batchSizeCalculator ?: ObjectManager::getInstance()->get(
+ BatchSizeCalculator::class
);
- $this->activeTableSwitcher = $activeTableSwitcher ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
+ $this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get(
ActiveTableSwitcher::class
);
+ $this->batchQueryGenerator = $batchQueryGenerator ?: ObjectManager::getInstance()->get(
+ QueryGenerator::class
+ );
}
/**
@@ -81,10 +106,10 @@ public function __construct(
*
* @param array|int|null $ids
* @return void
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function execute($ids = null)
+ public function execute($ids = null): void
{
if (!$this->isEavIndexerEnabled()) {
return;
@@ -94,20 +119,21 @@ public function execute($ids = null)
$connection = $indexer->getConnection();
$mainTable = $this->activeTableSwitcher->getAdditionalTableName($indexer->getMainTable());
$connection->truncateTable($mainTable);
- $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
- $batches = $this->batchProvider->getBatches(
- $connection,
- $entityMetadata->getEntityTable(),
+ $entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
+
+ $select = $connection->select();
+ $select->distinct(true);
+ $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+
+ $batchQueries = $this->batchQueryGenerator->generate(
$entityMetadata->getIdentifierField(),
- $this->batchSizeCalculator->estimateBatchSize($connection, $indexerName)
+ $select,
+ $this->batchSizeCalculator->estimateBatchSize($connection, $indexerName),
+ BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR
);
- foreach ($batches as $batch) {
- /** @var \Magento\Framework\DB\Select $select */
- $select = $connection->select();
- $select->distinct(true);
- $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
- $entityIds = $this->batchProvider->getBatchIds($connection, $select, $batch);
+ foreach ($batchQueries as $query) {
+ $entityIds = $connection->fetchCol($query);
if (!empty($entityIds)) {
$indexer->reindexEntities($this->processRelations($indexer, $entityIds, true));
$this->syncData($indexer, $mainTable);
@@ -116,14 +142,14 @@ public function execute($ids = null)
$this->activeTableSwitcher->switchTable($indexer->getConnection(), [$indexer->getMainTable()]);
}
} catch (\Exception $e) {
- throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage()), $e);
+ throw new LocalizedException(__($e->getMessage()), $e);
}
}
/**
* @inheritdoc
*/
- protected function syncData($indexer, $destinationTable, $ids = null)
+ protected function syncData($indexer, $destinationTable, $ids = null): void
{
$connection = $indexer->getConnection();
$connection->beginTransaction();
@@ -136,7 +162,7 @@ protected function syncData($indexer, $destinationTable, $ids = null)
$select,
$destinationTable,
$targetColumns,
- \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::INSERT_ON_DUPLICATE
);
$connection->query($query);
$connection->commit();
@@ -155,7 +181,7 @@ private function isEavIndexerEnabled(): bool
{
$eavIndexerStatus = $this->scopeConfig->getValue(
self::ENABLE_EAV_INDEXER,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE
);
return (bool)$eavIndexerStatus;
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 8182e6f07fab..ad734b96d59d 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Eraser.php
@@ -8,7 +8,12 @@
namespace Magento\Catalog\Model\Indexer\Product\Flat\Action;
use Magento\Framework\App\ResourceConnection;
+use Magento\Catalog\Model\Product\Attribute\Source\Status;
+use Magento\Store\Model\Store;
+/**
+ * Flat item eraser. Used to clear items from the catalog flat table.
+ */
class Eraser
{
/**
@@ -50,12 +55,7 @@ public function __construct(
*/
public function removeDeletedProducts(array &$ids, $storeId)
{
- $select = $this->connection->select()->from(
- $this->productIndexerHelper->getTable('catalog_product_entity')
- )->where(
- 'entity_id IN(?)',
- $ids
- );
+ $select = $this->getSelectForProducts($ids);
$result = $this->connection->query($select);
$existentProducts = [];
@@ -69,6 +69,62 @@ public function removeDeletedProducts(array &$ids, $storeId)
$this->deleteProductsFromStore($productsToDelete, $storeId);
}
+ /**
+ * Remove products with "Disabled" status from the flat table(s).
+ *
+ * @param array $ids
+ * @param int $storeId
+ * @return void
+ */
+ public function removeDisabledProducts(array &$ids, $storeId)
+ {
+ /* @var $statusAttribute \Magento\Eav\Model\Entity\Attribute */
+ $statusAttribute = $this->productIndexerHelper->getAttribute('status');
+
+ $select = $this->getSelectForProducts($ids);
+ $select->joinLeft(
+ ['status_global_attr' => $statusAttribute->getBackendTable()],
+ ' status_global_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId()
+ . ' AND status_global_attr.store_id = ' . Store::DEFAULT_STORE_ID,
+ []
+ );
+ $select->joinLeft(
+ ['status_attr' => $statusAttribute->getBackendTable()],
+ ' status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId()
+ . ' AND status_attr.store_id = ' . $storeId,
+ []
+ );
+ $select->where('IFNULL(status_attr.value, status_global_attr.value) = ?', Status::STATUS_DISABLED);
+
+ $result = $this->connection->query($select);
+
+ $disabledProducts = [];
+ foreach ($result->fetchAll() as $product) {
+ $disabledProducts[] = $product['entity_id'];
+ }
+
+ if (!empty($disabledProducts)) {
+ $ids = array_diff($ids, $disabledProducts);
+ $this->deleteProductsFromStore($disabledProducts, $storeId);
+ }
+ }
+
+ /**
+ * Get Select object for existed products.
+ *
+ * @param array $ids
+ * @return \Magento\Framework\DB\Select
+ */
+ private function getSelectForProducts(array $ids)
+ {
+ $productTable = $this->productIndexerHelper->getTable('catalog_product_entity');
+ $select = $this->connection->select()
+ ->from(['product_table' => $productTable])
+ ->columns('entity_id')
+ ->where('product_table.entity_id IN(?)', $ids);
+ return $select;
+ }
+
/**
* Delete products from flat table(s)
*
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 3a1611299288..c14bc0dd7e50 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
@@ -53,42 +54,39 @@ public function __construct(
* @param int $storeId
* @param int $productId
* @param string $valueFieldSuffix
- * @return \Magento\Catalog\Model\Indexer\Product\Flat
+ * @return $this
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function write($storeId, $productId, $valueFieldSuffix = '')
{
$flatTable = $this->_productIndexerHelper->getFlatTableName($storeId);
+ $entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity');
$attributes = $this->_productIndexerHelper->getAttributes();
$eavAttributes = $this->_productIndexerHelper->getTablesStructure($attributes);
$updateData = [];
$describe = $this->_connection->describeTable($flatTable);
+ $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class);
+ $linkField = $metadata->getLinkField();
foreach ($eavAttributes as $tableName => $tableColumns) {
$columnsChunks = array_chunk($tableColumns, self::ATTRIBUTES_CHUNK_SIZE, true);
foreach ($columnsChunks as $columns) {
$select = $this->_connection->select();
- $selectValue = $this->_connection->select();
- $keyColumns = [
- 'entity_id' => 'e.entity_id',
- 'attribute_id' => 't.attribute_id',
- 'value' => $this->_connection->getIfNullSql('`t2`.`value`', '`t`.`value`'),
- ];
-
- if ($tableName != $this->_productIndexerHelper->getTable('catalog_product_entity')) {
+
+ if ($tableName != $entityTableName) {
$valueColumns = [];
$ids = [];
$select->from(
- ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')],
- $keyColumns
- );
-
- $selectValue->from(
- ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')],
- $keyColumns
+ ['e' => $entityTableName],
+ [
+ 'entity_id' => 'e.entity_id',
+ 'attribute_id' => 't.attribute_id',
+ 'value' => 't.value'
+ ]
);
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
@@ -97,40 +95,35 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
$ids[$attribute->getId()] = $columnName;
}
}
- $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
- $select->joinLeft(
+ $select->joinInner(
['t' => $tableName],
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
- )->where(
- 't.attribute_id IS NOT NULL'
- );
+ )->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')],
@@ -139,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'];
}
@@ -156,8 +149,9 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
$columnNames = array_keys($columns);
$columnNames[] = 'attribute_set_id';
$columnNames[] = 'type_id';
+ $columnNames[] = $linkField;
$select->from(
- ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')],
+ ['e' => $entityTableName],
$columnNames
)->where(
'e.entity_id = ' . $productId
@@ -165,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;
}
@@ -175,7 +170,9 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
if (!empty($updateData)) {
$updateData += ['entity_id' => $productId];
- $updateData += ['row_id' => $productId];
+ if ($linkField !== $metadata->getIdentifierField()) {
+ $updateData += [$linkField => $linkFieldId];
+ }
$updateFields = [];
foreach ($updateData as $key => $value) {
$updateFields[$key] = $key;
@@ -187,6 +184,8 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
}
/**
+ * Get MetadataPool instance
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
*/
private function getMetadataPool()
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 b5dbdb68606f..64a7c4be4e03 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php
@@ -5,16 +5,20 @@
*/
namespace Magento\Catalog\Model\Indexer\Product\Flat\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder;
use Magento\Catalog\Model\Indexer\Product\Flat\TableBuilder;
+use Magento\Framework\EntityManager\MetadataPool;
/**
- * Class Row reindex action
+ * Class Row reindex action.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Flat\Action\Indexer
+ * @var Indexer
*/
protected $flatItemWriter;
@@ -23,6 +27,11 @@ class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction
*/
protected $flatItemEraser;
+ /**
+ * @var MetadataPool
+ */
+ private $metadataPool;
+
/**
* @param \Magento\Framework\App\ResourceConnection $resource
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -32,6 +41,7 @@ class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction
* @param FlatTableBuilder $flatTableBuilder
* @param Indexer $flatItemWriter
* @param Eraser $flatItemEraser
+ * @param MetadataPool|null $metadataPool
*/
public function __construct(
\Magento\Framework\App\ResourceConnection $resource,
@@ -41,7 +51,8 @@ public function __construct(
TableBuilder $tableBuilder,
FlatTableBuilder $flatTableBuilder,
Indexer $flatItemWriter,
- Eraser $flatItemEraser
+ Eraser $flatItemEraser,
+ MetadataPool $metadataPool = null
) {
parent::__construct(
$resource,
@@ -53,6 +64,8 @@ public function __construct(
);
$this->flatItemWriter = $flatItemWriter;
$this->flatItemEraser = $flatItemEraser;
+ $this->metadataPool = $metadataPool ?:
+ \Magento\Framework\App\ObjectManager::getInstance()->get(MetadataPool::class);
}
/**
@@ -70,24 +83,50 @@ public function execute($id = null)
);
}
$ids = [$id];
- foreach ($this->_storeManager->getStores() as $store) {
+ $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
+
+ $stores = $this->_storeManager->getStores();
+ foreach ($stores as $store) {
$tableExists = $this->_isFlatTableExists($store->getId());
if ($tableExists) {
$this->flatItemEraser->removeDeletedProducts($ids, $store->getId());
+ $this->flatItemEraser->removeDisabledProducts($ids, $store->getId());
}
- if (isset($ids[0])) {
+
+ /* @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 = [
+ '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(['e' => $catalogProductEntityTable], ['s.value'])
+ ->where(implode(' AND ', $statusConditions))
+ ->joinLeft(['s' => $statusTable], "e.{$linkField} = s.{$linkField}", [])
+ ->order('s.store_id DESC')
+ ->limit(1);
+ $result = $this->_connection->query($select);
+ $status = $result->fetchColumn(0);
+
+ if ($status == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) {
if (!$tableExists) {
$this->_flatTableBuilder->build(
$store->getId(),
- [$ids[0]],
+ $ids,
$this->_valueFieldSuffix,
$this->_tableDropSuffix,
false
);
}
- $this->flatItemWriter->write($store->getId(), $ids[0], $this->_valueFieldSuffix);
+ $this->flatItemWriter->write($store->getId(), $id, $this->_valueFieldSuffix);
+ } else {
+ $this->flatItemEraser->deleteProductsFromStore($id, $store->getId());
}
}
+
return $this;
}
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php
index fbe0d4b550fa..2252b3e3d550 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php
@@ -10,7 +10,8 @@
use Magento\Framework\EntityManager\MetadataPool;
/**
- * Class FlatTableBuilder
+ * Class for building flat index
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class FlatTableBuilder
@@ -346,12 +347,21 @@ protected function _updateTemporaryTableByStoreValues(
}
//Update not simple attributes (eg. dropdown)
- if (isset($flatColumns[$attributeCode . $valueFieldSuffix])) {
- $select = $this->_connection->select()->joinInner(
- ['t' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')],
- 't.option_id = et.' . $attributeCode . ' AND t.store_id=' . $storeId,
- [$attributeCode . $valueFieldSuffix => 't.value']
- );
+ $columnName = $attributeCode . $valueFieldSuffix;
+ if (isset($flatColumns[$columnName])) {
+ $columnValue = $this->_connection->getIfNullSql('ts.value', 't0.value');
+ $select = $this->_connection->select();
+ $select->joinLeft(
+ ['t0' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')],
+ 't0.option_id = et.' . $attributeCode . ' AND t0.store_id = 0',
+ []
+ )->joinLeft(
+ ['ts' => $this->_productIndexerHelper->getTable('eav_attribute_option_value')],
+ 'ts.option_id = et.' . $attributeCode . ' AND ts.store_id = ' . $storeId,
+ []
+ )->columns(
+ [$columnName => $columnValue]
+ )->where($columnValue . ' IS NOT NULL');
if (!empty($changedIds)) {
$select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds));
}
@@ -374,6 +384,8 @@ protected function _getTemporaryTableName($tableName)
}
/**
+ * Get metadata pool
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
*/
private function getMetadataPool()
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php
index a32379b8c0a6..e6c098ab0254 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
{
/**
@@ -112,7 +115,7 @@ public function build($storeId, $changedIds, $valueFieldSuffix)
/**
* Create empty temporary table with given columns list
*
- * @param string $tableName Table name
+ * @param string $tableName Table name
* @param array $columns array('columnName' => \Magento\Catalog\Model\ResourceModel\Eav\Attribute, ...)
* @param string $valueFieldSuffix
*
@@ -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,
@@ -289,12 +304,16 @@ protected function _fillTemporaryTable(
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
foreach ($columnsList as $columnName => $attribute) {
- $countTableName = 't' . $iterationNum++;
+ $countTableName = 't' . ($iterationNum++);
$joinCondition = sprintf(
- 'e.%3$s = %1$s.%3$s AND %1$s.attribute_id = %2$d AND %1$s.store_id = 0',
+ 'e.%3$s = %1$s.%3$s' .
+ ' AND %1$s.attribute_id = %2$d' .
+ ' AND (%1$s.store_id = %4$d' .
+ ' OR %1$s.store_id = 0)',
$countTableName,
$attribute->getId(),
- $metadata->getLinkField()
+ $metadata->getLinkField(),
+ $storeId
);
$select->joinLeft(
@@ -308,9 +327,10 @@ protected function _fillTemporaryTable(
$columnValueName = $attributeCode . $valueFieldSuffix;
if (isset($flatColumns[$columnValueName])) {
$valueJoinCondition = sprintf(
- 'e.%1$s = %2$s.option_id AND %2$s.store_id = 0',
+ 'e.%1$s = %2$s.option_id AND (%2$s.store_id = %3$d OR %2$s.store_id = 0)',
$attributeCode,
- $countTableName
+ $countTableName,
+ $storeId
);
$selectValue->joinLeft(
[
@@ -345,6 +365,8 @@ protected function _fillTemporaryTable(
}
/**
+ * Get Metadata Pool
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
* @deprecated 101.1.0
*/
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
index 1a7575157065..858eba3ab217 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
@@ -3,41 +3,64 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Indexer\Product\Price\Action;
+use Magento\Catalog\Model\Indexer\Product\Price\AbstractAction;
+use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory;
+use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer;
+use Magento\Catalog\Model\Product\Type;
+use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory;
+use Magento\Directory\Model\CurrencyFactory;
+use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Query\BatchIterator;
+use Magento\Framework\DB\Query\Generator as QueryGenerator;
+use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\EntityMetadataInterface;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Indexer\BatchProviderInterface;
use Magento\Framework\Indexer\DimensionalIndexerInterface;
use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
+use Magento\Framework\Stdlib\DateTime;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Indexer\Model\ProcessManager;
use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class Full reindex action
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
+class Full extends AbstractAction
{
/**
- * @var \Magento\Framework\EntityManager\MetadataPool
+ * @var MetadataPool
*/
private $metadataPool;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator
+ * @var BatchSizeCalculator
*/
private $batchSizeCalculator;
/**
- * @var \Magento\Framework\Indexer\BatchProviderInterface
+ * @var BatchProviderInterface
*/
private $batchProvider;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher
+ * @var ActiveTableSwitcher
*/
private $activeTableSwitcher;
@@ -47,54 +70,61 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
private $productMetaDataCached;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory
+ * @var DimensionCollectionFactory
*/
private $dimensionCollectionFactory;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer
+ * @var TableMaintainer
*/
private $dimensionTableMaintainer;
/**
- * @var \Magento\Indexer\Model\ProcessManager
+ * @var ProcessManager
*/
private $processManager;
/**
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory
- * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
- * @param \Magento\Framework\Stdlib\DateTime $dateTime
- * @param \Magento\Catalog\Model\Product\Type $catalogProductType
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource
- * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator|null $batchSizeCalculator
- * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
- * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher
- * @param \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory|null $dimensionCollectionFactory
- * @param \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer|null $dimensionTableMaintainer
- * @param \Magento\Indexer\Model\ProcessManager $processManager
+ * @var QueryGenerator|null
+ */
+ private $batchQueryGenerator;
+
+ /**
+ * @param ScopeConfigInterface $config
+ * @param StoreManagerInterface $storeManager
+ * @param CurrencyFactory $currencyFactory
+ * @param TimezoneInterface $localeDate
+ * @param DateTime $dateTime
+ * @param Type $catalogProductType
+ * @param Factory $indexerPriceFactory
+ * @param DefaultPrice $defaultIndexerResource
+ * @param MetadataPool|null $metadataPool
+ * @param BatchSizeCalculator|null $batchSizeCalculator
+ * @param BatchProviderInterface|null $batchProvider
+ * @param ActiveTableSwitcher|null $activeTableSwitcher
+ * @param DimensionCollectionFactory|null $dimensionCollectionFactory
+ * @param TableMaintainer|null $dimensionTableMaintainer
+ * @param ProcessManager $processManager
+ * @param QueryGenerator|null $batchQueryGenerator
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Framework\App\Config\ScopeConfigInterface $config,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Directory\Model\CurrencyFactory $currencyFactory,
- \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
- \Magento\Framework\Stdlib\DateTime $dateTime,
- \Magento\Catalog\Model\Product\Type $catalogProductType,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator $batchSizeCalculator = null,
- \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
- \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null,
- \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory = null,
- \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $dimensionTableMaintainer = null,
- \Magento\Indexer\Model\ProcessManager $processManager = null
+ ScopeConfigInterface $config,
+ StoreManagerInterface $storeManager,
+ CurrencyFactory $currencyFactory,
+ TimezoneInterface $localeDate,
+ DateTime $dateTime,
+ Type $catalogProductType,
+ Factory $indexerPriceFactory,
+ DefaultPrice $defaultIndexerResource,
+ MetadataPool $metadataPool = null,
+ BatchSizeCalculator $batchSizeCalculator = null,
+ BatchProviderInterface $batchProvider = null,
+ ActiveTableSwitcher $activeTableSwitcher = null,
+ DimensionCollectionFactory $dimensionCollectionFactory = null,
+ TableMaintainer $dimensionTableMaintainer = null,
+ ProcessManager $processManager = null,
+ QueryGenerator $batchQueryGenerator = null
) {
parent::__construct(
$config,
@@ -107,26 +137,27 @@ public function __construct(
$defaultIndexerResource
);
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(
- \Magento\Framework\EntityManager\MetadataPool::class
+ MetadataPool::class
);
$this->batchSizeCalculator = $batchSizeCalculator ?: ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator::class
+ BatchSizeCalculator::class
);
$this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get(
- \Magento\Framework\Indexer\BatchProviderInterface::class
+ BatchProviderInterface::class
);
$this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class
+ ActiveTableSwitcher::class
);
$this->dimensionCollectionFactory = $dimensionCollectionFactory ?: ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class
+ DimensionCollectionFactory::class
);
$this->dimensionTableMaintainer = $dimensionTableMaintainer ?: ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class
+ TableMaintainer::class
);
$this->processManager = $processManager ?: ObjectManager::getInstance()->get(
- \Magento\Indexer\Model\ProcessManager::class
+ ProcessManager::class
);
+ $this->batchQueryGenerator = $batchQueryGenerator ?? ObjectManager::getInstance()->get(QueryGenerator::class);
}
/**
@@ -137,13 +168,13 @@ public function __construct(
* @throws \Exception
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function execute($ids = null)
+ public function execute($ids = null): void
{
try {
//Prepare indexer tables before full reindex
$this->prepareTables();
- /** @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $indexer */
+ /** @var DefaultPrice $indexer */
foreach ($this->getTypeIndexers(true) as $typeId => $priceIndexer) {
if ($priceIndexer instanceof DimensionalIndexerInterface) {
//New price reindex mechanism
@@ -170,7 +201,7 @@ public function execute($ids = null)
* @return void
* @throws \Exception
*/
- private function prepareTables()
+ private function prepareTables(): void
{
$this->_defaultIndexerResource->getTableStrategy()->setUseIdxTable(false);
@@ -185,7 +216,7 @@ private function prepareTables()
* @return void
* @throws \Exception
*/
- private function truncateReplicaTables()
+ private function truncateReplicaTables(): void
{
foreach ($this->dimensionCollectionFactory->create() as $dimension) {
$dimensionTable = $this->dimensionTableMaintainer->getMainReplicaTable($dimension);
@@ -202,12 +233,12 @@ private function truncateReplicaTables()
* @return void
* @throws \Exception
*/
- private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId)
+ private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId): void
{
$userFunctions = [];
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$userFunctions[] = function () use ($priceIndexer, $dimensions, $typeId) {
- return $this->reindexByBatches($priceIndexer, $dimensions, $typeId);
+ $this->reindexByBatches($priceIndexer, $dimensions, $typeId);
};
}
$this->processManager->execute($userFunctions);
@@ -223,10 +254,13 @@ private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $p
* @return void
* @throws \Exception
*/
- private function reindexByBatches(DimensionalIndexerInterface $priceIndexer, array $dimensions, string $typeId)
- {
+ private function reindexByBatches(
+ DimensionalIndexerInterface $priceIndexer,
+ array $dimensions,
+ string $typeId
+ ): void {
foreach ($this->getBatchesForIndexer($typeId) as $batch) {
- $this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions, $typeId);
+ $this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions);
}
}
@@ -235,16 +269,20 @@ private function reindexByBatches(DimensionalIndexerInterface $priceIndexer, arr
*
* @param string $typeId
*
- * @return \Generator
+ * @return BatchIterator
* @throws \Exception
*/
- private function getBatchesForIndexer(string $typeId)
+ private function getBatchesForIndexer(string $typeId): BatchIterator
{
$connection = $this->_defaultIndexerResource->getConnection();
- return $this->batchProvider->getBatches(
- $connection,
- $this->getProductMetaData()->getEntityTable(),
+ $entityMetadata = $this->getProductMetaData();
+ $select = $connection->select();
+ $select->distinct(true);
+ $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+
+ return $this->batchQueryGenerator->generate(
$this->getProductMetaData()->getIdentifierField(),
+ $select,
$this->batchSizeCalculator->estimateBatchSize(
$connection,
$typeId
@@ -256,20 +294,18 @@ private function getBatchesForIndexer(string $typeId)
* Reindex by batch for new 'Dimensional' price indexer
*
* @param DimensionalIndexerInterface $priceIndexer
- * @param array $batch
+ * @param Select $batchQuery
* @param array $dimensions
- * @param string $typeId
*
* @return void
* @throws \Exception
*/
private function reindexByBatchWithDimensions(
DimensionalIndexerInterface $priceIndexer,
- array $batch,
- array $dimensions,
- string $typeId
- ) {
- $entityIds = $this->getEntityIdsFromBatch($typeId, $batch);
+ Select $batchQuery,
+ array $dimensions
+ ): void {
+ $entityIds = $this->getEntityIdsFromBatch($batchQuery);
if (!empty($entityIds)) {
$this->dimensionTableMaintainer->createMainTmpTable($dimensions);
@@ -295,10 +331,10 @@ private function reindexByBatchWithDimensions(
* @return void
* @throws \Exception
*/
- private function reindexProductType(PriceInterface $priceIndexer, string $typeId)
+ private function reindexProductType(PriceInterface $priceIndexer, string $typeId): void
{
foreach ($this->getBatchesForIndexer($typeId) as $batch) {
- $this->reindexBatch($priceIndexer, $batch, $typeId);
+ $this->reindexBatch($priceIndexer, $batch);
}
}
@@ -306,15 +342,13 @@ private function reindexProductType(PriceInterface $priceIndexer, string $typeId
* Reindex by batch for old price indexer
*
* @param PriceInterface $priceIndexer
- * @param array $batch
- * @param string $typeId
- *
+ * @param Select $batch
* @return void
* @throws \Exception
*/
- private function reindexBatch(PriceInterface $priceIndexer, array $batch, string $typeId)
+ private function reindexBatch(PriceInterface $priceIndexer, Select $batch): void
{
- $entityIds = $this->getEntityIdsFromBatch($typeId, $batch);
+ $entityIds = $this->getEntityIdsFromBatch($batch);
if (!empty($entityIds)) {
// Temporary table will created if not exists
@@ -339,27 +373,15 @@ private function reindexBatch(PriceInterface $priceIndexer, array $batch, string
/**
* Get Entity Ids from batch
*
- * @param string $typeId
- * @param array $batch
- *
+ * @param Select $batch
* @return array
* @throws \Exception
*/
- private function getEntityIdsFromBatch(string $typeId, array $batch)
+ private function getEntityIdsFromBatch(Select $batch): array
{
$connection = $this->_defaultIndexerResource->getConnection();
- // Get entity ids from batch
- $select = $connection
- ->select()
- ->distinct(true)
- ->from(
- ['e' => $this->getProductMetaData()->getEntityTable()],
- $this->getProductMetaData()->getIdentifierField()
- )
- ->where('type_id = ?', $typeId);
-
- return $this->batchProvider->getBatchIds($connection, $select, $batch);
+ return $connection->fetchCol($batch);
}
/**
@@ -368,7 +390,7 @@ private function getEntityIdsFromBatch(string $typeId, array $batch)
* @return EntityMetadataInterface
* @throws \Exception
*/
- private function getProductMetaData()
+ private function getProductMetaData(): EntityMetadataInterface
{
if ($this->productMetaDataCached === null) {
$this->productMetaDataCached = $this->metadataPool->getMetadata(ProductInterface::class);
@@ -383,7 +405,7 @@ private function getProductMetaData()
* @return string
* @throws \Exception
*/
- private function getReplicaTable()
+ private function getReplicaTable(): string
{
return $this->activeTableSwitcher->getAdditionalTableName(
$this->_defaultIndexerResource->getMainTable()
@@ -394,8 +416,9 @@ private function getReplicaTable()
* Replacement of tables from replica to main
*
* @return void
+ * @throws \Zend_Db_Statement_Exception
*/
- private function switchTables()
+ private function switchTables(): void
{
// Switch dimension tables
$mainTablesByDimension = [];
@@ -417,13 +440,14 @@ private function switchTables()
/**
* Move data from old price indexer mechanism to new indexer mechanism by dimensions.
+ *
* Used only for backward compatibility
*
* @param array $dimensions
- *
* @return void
+ * @throws \Zend_Db_Statement_Exception
*/
- private function moveDataFromReplicaTableToReplicaTables(array $dimensions)
+ private function moveDataFromReplicaTableToReplicaTables(array $dimensions): void
{
if (!$dimensions) {
return;
@@ -455,17 +479,17 @@ private function moveDataFromReplicaTableToReplicaTables(array $dimensions)
$select,
$replicaTablesByDimension,
[],
- \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::INSERT_ON_DUPLICATE
)
);
}
/**
- * @deprecated
+ * Retrieves the index table that should be used
*
- * @inheritdoc
+ * @deprecated
*/
- protected function getIndexTargetTable()
+ protected function getIndexTargetTable(): string
{
return $this->activeTableSwitcher->getAdditionalTableName($this->_defaultIndexerResource->getMainTable());
}
diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php
index d21a8666ec0a..f2e2e67f944e 100644
--- a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php
+++ b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php
@@ -139,7 +139,7 @@ public function apply(\Magento\Framework\App\RequestInterface $request)
}
/**
- * Get fiter items count
+ * Get filter items count
*
* @return int
*/
diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/Decimal.php b/app/code/Magento/Catalog/Model/Layer/Filter/Decimal.php
index dac2632ff6db..d76711cb21db 100644
--- a/app/code/Magento/Catalog/Model/Layer/Filter/Decimal.php
+++ b/app/code/Magento/Catalog/Model/Layer/Filter/Decimal.php
@@ -32,8 +32,8 @@ class Decimal extends \Magento\Catalog\Model\Layer\Filter\AbstractFilter
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Model\Layer $layer
* @param \Magento\Catalog\Model\Layer\Filter\Item\DataBuilder $itemDataBuilder
- * @param \Magento\Catalog\Model\ResourceModel\Layer\Filter\DecimalFactory $filterDecimalFactory
* @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
+ * @param \Magento\Catalog\Model\Layer\Filter\DataProvider\DecimalFactory $dataProviderFactory
* @param array $data
*/
public function __construct(
diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php b/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php
index 4d2878b0b1e8..07c9c2eaa249 100644
--- a/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php
+++ b/app/code/Magento/Catalog/Model/Layer/Filter/Item/DataBuilder.php
@@ -4,11 +4,11 @@
* See COPYING.txt for license details.
*/
+namespace Magento\Catalog\Model\Layer\Filter\Item;
+
/**
* Item Data Builder
*/
-namespace Magento\Catalog\Model\Layer\Filter\Item;
-
class DataBuilder
{
/**
@@ -29,7 +29,7 @@ class DataBuilder
* Add Item Data
*
* @param string $label
- * @param string $label
+ * @param string $value
* @param int $count
* @return void
*/
diff --git a/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php b/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php
index c88215d92357..f51b2e4f90a6 100644
--- a/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php
+++ b/app/code/Magento/Catalog/Model/Plugin/ProductRepository/TransactionWrapper.php
@@ -7,6 +7,9 @@
*/
namespace Magento\Catalog\Model\Plugin\ProductRepository;
+/**
+ * Transaction wrapper for product repository CRUD.
+ */
class TransactionWrapper
{
/**
@@ -24,8 +27,10 @@ public function __construct(
}
/**
+ * Transaction wrapper for save action.
+ *
* @param \Magento\Catalog\Api\ProductRepositoryInterface $subject
- * @param callable $proceed
+ * @param \Closure $proceed
* @param \Magento\Catalog\Api\Data\ProductInterface $product
* @param bool $saveOptions
* @return \Magento\Catalog\Api\Data\ProductInterface
@@ -51,8 +56,10 @@ public function aroundSave(
}
/**
+ * Transaction wrapper for delete action.
+ *
* @param \Magento\Catalog\Api\ProductRepositoryInterface $subject
- * @param callable $proceed
+ * @param \Closure $proceed
* @param \Magento\Catalog\Api\Data\ProductInterface $product
* @return bool
* @throws \Exception
@@ -76,8 +83,10 @@ public function aroundDelete(
}
/**
+ * Transaction wrapper for delete by id action.
+ *
* @param \Magento\Catalog\Api\ProductRepositoryInterface $subject
- * @param callable $proceed
+ * @param \Closure $proceed
* @param string $productSku
* @return bool
* @throws \Exception
diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php
index b29eee9a47f8..1e774e45df41 100644
--- a/app/code/Magento/Catalog/Model/Product.php
+++ b/app/code/Magento/Catalog/Model/Product.php
@@ -498,21 +498,26 @@ protected function _getResource()
}
/**
- * Get a list of custom attribute codes that belongs to product attribute set. If attribute set not specified for
- * product will return all product attribute codes
+ * Get a list of custom attribute codes that belongs to product attribute set.
+ *
+ * If attribute set not specified for product will return all product attribute codes
*
* @return string[]
*/
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;
@@ -526,9 +531,9 @@ protected function getCustomAttributesCodes()
public function getStoreId()
{
if ($this->hasData(self::STORE_ID)) {
- return $this->getData(self::STORE_ID);
+ return (int)$this->getData(self::STORE_ID);
}
- return $this->_storeManager->getStore()->getId();
+ return (int)$this->_storeManager->getStore()->getId();
}
/**
@@ -584,8 +589,9 @@ public function getPrice()
}
/**
- * @codeCoverageIgnoreStart
* Get visibility status
+ *
+ * @codeCoverageIgnoreStart
* @see \Magento\Catalog\Model\Product\Visibility
*
* @return int
@@ -662,6 +668,7 @@ public function getStatus()
/**
* Retrieve type instance of the product.
+ *
* Type instance implements product type depended logic and is a singleton shared by all products of the same type.
*
* @return \Magento\Catalog\Model\Product\Type\AbstractType
@@ -715,7 +722,7 @@ public function getIdBySku($sku)
public function getCategoryId()
{
$category = $this->_registry->registry('current_category');
- if ($category) {
+ if ($category && in_array($category->getId(), $this->getCategoryIds())) {
return $category->getId();
}
return false;
@@ -810,6 +817,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);
@@ -822,9 +832,10 @@ public function getStoreIds()
/**
* Retrieve product attributes
- * if $groupId is null - retrieve all product attributes
*
- * @param int $groupId Retrieve attributes of the specified group
+ * If $groupId is null - retrieve all product attributes
+ *
+ * @param int $groupId Retrieve attributes of the specified group
* @param bool $skipSuper Not used
* @return \Magento\Eav\Model\Entity\Attribute\AbstractAttribute[]
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
@@ -916,10 +927,11 @@ public function beforeSave()
/**
* Check/set if options can be affected when saving product
+ *
* If value specified, it will be set.
*
- * @param bool $value
- * @return bool
+ * @param bool $value
+ * @return bool
*/
public function canAffectOptions($value = null)
{
@@ -976,7 +988,7 @@ public function setQty($qty)
*/
public function getQty()
{
- return $this->getData('qty');
+ return (float)$this->getData('qty');
}
/**
@@ -1036,9 +1048,11 @@ public function reindex()
/**
* Clear cache related with product and protect delete from not admin
+ *
* Register indexing event before delete product
*
* @return \Magento\Catalog\Model\Product
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function beforeDelete()
{
@@ -1545,12 +1559,13 @@ public function hasGalleryAttribute()
/**
* Add image to media gallery
*
- * @param string $file file path of image in file system
- * @param string|array $mediaAttribute code of attribute with type 'media_image',
- * leave blank if image should be only in gallery
- * @param boolean $move if true, it will move source file
- * @param boolean $exclude mark image as disabled in product page view
+ * @param string $file file path of image in file system
+ * @param string|array $mediaAttribute code of attribute with type 'media_image',
+ * leave blank if image should be only in gallery
+ * @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)
{
@@ -1711,7 +1726,6 @@ public function getIsSalable()
/**
* Check is a virtual product
- * Data helper wrapper
*
* @return bool
*/
@@ -1804,8 +1818,8 @@ public function formatUrlKey($str)
* Save current attribute with code $code and assign new value
*
* @param string $code Attribute code
- * @param mixed $value New attribute value
- * @param int $store Store ID
+ * @param mixed $value New attribute value
+ * @param int $store Store ID
* @return void
*/
public function addAttributeUpdate($code, $value, $store)
@@ -1875,6 +1889,7 @@ public function getRequestPath()
/**
* Custom function for other modules
+ *
* @return string
*/
public function getGiftMessageAvailable()
@@ -1993,6 +2008,8 @@ public function getOptions()
}
/**
+ * Set product options
+ *
* @param \Magento\Catalog\Api\Data\ProductCustomOptionInterface[] $options
* @return $this
*/
@@ -2016,10 +2033,10 @@ public function getIsVirtual()
/**
* Add custom option information to product
*
- * @param string $code Option code
- * @param mixed $value Value of the option
- * @param int|Product $product Product ID
- * @return $this
+ * @param string $code Option code
+ * @param mixed $value Value of the option
+ * @param int|Product|null $product Product ID
+ * @return $this
*/
public function addCustomOption($code, $value, $product = null)
{
@@ -2213,6 +2230,7 @@ public function getPreconfiguredValues()
/**
* Prepare product custom options.
+ *
* To be sure that all product custom options does not has ID and has product instance
*
* @return \Magento\Catalog\Model\Product
@@ -2547,9 +2565,9 @@ public function setTypeId($typeId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
- * @return \Magento\Catalog\Api\Data\ProductExtensionInterface
+ * @return \Magento\Framework\Api\ExtensionAttributesInterface
*/
public function getExtensionAttributes()
{
@@ -2557,7 +2575,7 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
* @param \Magento\Catalog\Api\Data\ProductExtensionInterface $extensionAttributes
* @return $this
@@ -2570,8 +2588,11 @@ public function setExtensionAttributes(\Magento\Catalog\Api\Data\ProductExtensio
//@codeCoverageIgnoreEnd
/**
+ * Convert Image to ProductAttributeMediaGalleryEntryInterface
+ *
* @param array $mediaGallery
* @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface[]
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function convertToMediaGalleryInterface(array $mediaGallery)
{
@@ -2587,7 +2608,10 @@ protected function convertToMediaGalleryInterface(array $mediaGallery)
}
/**
+ * Get media gallery entries
+ *
* @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface[]|null
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getMediaGalleryEntries()
{
@@ -2601,8 +2625,11 @@ public function getMediaGalleryEntries()
}
/**
+ * Set media gallery entries
+ *
* @param ProductAttributeMediaGalleryEntryInterface[] $mediaGalleryEntries
* @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function setMediaGalleryEntries(array $mediaGalleryEntries = null)
{
@@ -2643,6 +2670,8 @@ public function setId($value)
}
/**
+ * Get link repository
+ *
* @return ProductLinkRepositoryInterface
*/
private function getLinkRepository()
@@ -2655,6 +2684,8 @@ private function getLinkRepository()
}
/**
+ * Get media gallery processor
+ *
* @return Product\Gallery\Processor
*/
private function getMediaGalleryProcessor()
diff --git a/app/code/Magento/Catalog/Model/Product/Action.php b/app/code/Magento/Catalog/Model/Product/Action.php
index f78048424b42..3863cf245724 100644
--- a/app/code/Magento/Catalog/Model/Product/Action.php
+++ b/app/code/Magento/Catalog/Model/Product/Action.php
@@ -168,5 +168,7 @@ public function updateWebsites($productIds, $websiteIds, $type)
if (!$categoryIndexer->isScheduled()) {
$categoryIndexer->reindexList(array_unique($productIds));
}
+
+ $this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php
index c2108b0273bd..be1e523960f2 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Product\Attribute\Backend;
use Magento\Catalog\Model\Product\Attribute\Source\Boolean as BooleanSource;
@@ -25,7 +27,9 @@ public function beforeSave($object)
$attributeCode = $this->getAttribute()->getName();
if ($object->getData('use_config_' . $attributeCode)) {
$object->setData($attributeCode, BooleanSource::VALUE_USE_CONFIG);
+ return $this;
}
- return $this;
+
+ return parent::beforeSave($object);
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
index 3779cab431cb..e26717e47274 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/GroupPrice/AbstractGroupPrice.php
@@ -247,6 +247,8 @@ public function validate($object)
}
/**
+ * Validate price.
+ *
* @param array $priceRow
* @return void
* @throws \Magento\Framework\Exception\LocalizedException
@@ -312,6 +314,8 @@ public function afterLoad($object)
}
/**
+ * Get website id.
+ *
* @param int $storeId
* @return int|null
*/
@@ -327,6 +331,8 @@ private function getWebsiteId($storeId)
}
/**
+ * Set price data.
+ *
* @param \Magento\Catalog\Model\Product $object
* @param array $priceData
*/
@@ -373,122 +379,16 @@ protected function modifyPriceData($object, $data)
*
* @param \Magento\Catalog\Model\Product $object
* @return $this
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterSave($object)
{
- $websiteId = $this->_storeManager->getStore($object->getStoreId())->getWebsiteId();
- $isGlobal = $this->getAttribute()->isScopeGlobal() || $websiteId == 0;
-
- $priceRows = $object->getData($this->getAttribute()->getName());
- if (null === $priceRows) {
- return $this;
- }
-
- $priceRows = array_filter((array)$priceRows);
-
- $old = [];
- $new = [];
-
- // prepare original data for compare
- $origPrices = $object->getOrigData($this->getAttribute()->getName());
- if (!is_array($origPrices)) {
- $origPrices = [];
- }
- foreach ($origPrices as $data) {
- if ($data['website_id'] > 0 || $data['website_id'] == '0' && $isGlobal) {
- $key = implode(
- '-',
- array_merge(
- [$data['website_id'], $data['cust_group']],
- $this->_getAdditionalUniqueFields($data)
- )
- );
- $old[$key] = $data;
- }
- }
-
- // prepare data for save
- foreach ($priceRows as $data) {
- $hasEmptyData = false;
- foreach ($this->_getAdditionalUniqueFields($data) as $field) {
- if (empty($field)) {
- $hasEmptyData = true;
- break;
- }
- }
-
- if ($hasEmptyData || !isset($data['cust_group']) || !empty($data['delete'])) {
- continue;
- }
- if ($this->getAttribute()->isScopeGlobal() && $data['website_id'] > 0) {
- continue;
- }
- if (!$isGlobal && (int)$data['website_id'] == 0) {
- continue;
- }
-
- $key = implode(
- '-',
- array_merge([$data['website_id'], $data['cust_group']], $this->_getAdditionalUniqueFields($data))
- );
-
- $useForAllGroups = $data['cust_group'] == $this->_groupManagement->getAllCustomersGroup()->getId();
- $customerGroupId = !$useForAllGroups ? $data['cust_group'] : 0;
- $new[$key] = array_merge(
- $this->getAdditionalFields($data),
- [
- 'website_id' => $data['website_id'],
- 'all_groups' => $useForAllGroups ? 1 : 0,
- 'customer_group_id' => $customerGroupId,
- 'value' => isset($data['price']) ? $data['price'] : null,
- ],
- $this->_getAdditionalUniqueFields($data)
- );
- }
-
- $delete = array_diff_key($old, $new);
- $insert = array_diff_key($new, $old);
- $update = array_intersect_key($new, $old);
-
- $isChanged = false;
- $productId = $object->getData($this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField());
-
- if (!empty($delete)) {
- foreach ($delete as $data) {
- $this->_getResource()->deletePriceData($productId, null, $data['price_id']);
- $isChanged = true;
- }
- }
-
- if (!empty($insert)) {
- foreach ($insert as $data) {
- $price = new \Magento\Framework\DataObject($data);
- $price->setData(
- $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(),
- $productId
- );
- $this->_getResource()->savePriceData($price);
-
- $isChanged = true;
- }
- }
-
- if (!empty($update)) {
- $isChanged |= $this->updateValues($update, $old);
- }
-
- if ($isChanged) {
- $valueChangedKey = $this->getAttribute()->getName() . '_changed';
- $object->setData($valueChangedKey, 1);
- }
-
return $this;
}
/**
+ * Update values.
+ *
* @param array $valuesToUpdate
* @param array $oldValues
* @return boolean
@@ -544,6 +444,8 @@ public function getResource()
}
/**
+ * Get metadata pool.
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
*/
private function getMetadataPool()
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 a652d0ef9021..98738e055ca8 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/AbstractHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/AbstractHandler.php
new file mode 100644
index 000000000000..fc0f090937db
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/AbstractHandler.php
@@ -0,0 +1,101 @@
+groupManagement = $groupManagement;
+ }
+
+ /**
+ * Get additional tier price fields.
+ *
+ * @param array $objectArray
+ * @return array
+ */
+ protected function getAdditionalFields(array $objectArray): array
+ {
+ $percentageValue = $this->getPercentage($objectArray);
+
+ return [
+ 'value' => $percentageValue ? null : $objectArray['price'],
+ 'percentage_value' => $percentageValue ?: null,
+ ];
+ }
+
+ /**
+ * Check whether price has percentage value.
+ *
+ * @param array $priceRow
+ * @return float|null
+ */
+ protected function getPercentage(array $priceRow): ?float
+ {
+ return isset($priceRow['percentage_value']) && is_numeric($priceRow['percentage_value'])
+ ? (float)$priceRow['percentage_value']
+ : null;
+ }
+
+ /**
+ * Prepare tier price data by provided price row data.
+ *
+ * @param array $data
+ * @return array
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ protected function prepareTierPrice(array $data): array
+ {
+ $useForAllGroups = (int)$data['cust_group'] === $this->groupManagement->getAllCustomersGroup()->getId();
+ $customerGroupId = $useForAllGroups ? 0 : $data['cust_group'];
+ $tierPrice = array_merge(
+ $this->getAdditionalFields($data),
+ [
+ 'website_id' => $data['website_id'],
+ 'all_groups' => (int)$useForAllGroups,
+ 'customer_group_id' => $customerGroupId,
+ 'value' => $data['price'] ?? null,
+ 'qty' => $this->parseQty($data['price_qty']),
+ ]
+ );
+
+ return $tierPrice;
+ }
+
+ /**
+ * Parse quantity value into float.
+ *
+ * @param mixed $value
+ * @return float|int
+ */
+ protected function parseQty($value)
+ {
+ return $value * 1;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php
new file mode 100644
index 000000000000..9cb2ac014589
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php
@@ -0,0 +1,112 @@
+storeManager = $storeManager;
+ $this->attributeRepository = $attributeRepository;
+ $this->metadataPoll = $metadataPool;
+ $this->tierPriceResource = $tierPriceResource;
+ }
+
+ /**
+ * Set tier price data for product entity
+ *
+ * @param \Magento\Catalog\Api\Data\ProductInterface|object $entity
+ * @param array $arguments
+ * @return \Magento\Catalog\Api\Data\ProductInterface|object
+ * @throws \Magento\Framework\Exception\InputException
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function execute($entity, $arguments = [])
+ {
+ $attribute = $this->attributeRepository->get('tier_price');
+ $priceRows = $entity->getData($attribute->getName());
+ if (null !== $priceRows) {
+ if (!is_array($priceRows)) {
+ throw new \Magento\Framework\Exception\InputException(
+ __('Tier prices data should be array, but actually other type is received')
+ );
+ }
+ $websiteId = $this->storeManager->getStore($entity->getStoreId())->getWebsiteId();
+ $isGlobal = $attribute->isScopeGlobal() || $websiteId === 0;
+ $identifierField = $this->metadataPoll->getMetadata(ProductInterface::class)->getLinkField();
+ $priceRows = array_filter($priceRows);
+ $productId = (int) $entity->getData($identifierField);
+
+ // prepare and save data
+ foreach ($priceRows as $data) {
+ $isPriceWebsiteGlobal = (int)$data['website_id'] === 0;
+ if ($isGlobal === $isPriceWebsiteGlobal
+ || !empty($data['price_qty'])
+ || isset($data['cust_group'])
+ ) {
+ $tierPrice = $this->prepareTierPrice($data);
+ $price = new \Magento\Framework\DataObject($tierPrice);
+ $price->setData(
+ $identifierField,
+ $productId
+ );
+ $this->tierPriceResource->savePriceData($price);
+ $valueChangedKey = $attribute->getName() . '_changed';
+ $entity->setData($valueChangedKey, 1);
+ }
+ }
+ }
+
+ return $entity;
+ }
+}
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
new file mode 100644
index 000000000000..663b7facf425
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php
@@ -0,0 +1,260 @@
+storeManager = $storeManager;
+ $this->attributeRepository = $attributeRepository;
+ $this->metadataPoll = $metadataPool;
+ $this->tierPriceResource = $tierPriceResource;
+ }
+
+ /**
+ * Perform action on relation/extension attribute.
+ *
+ * @param \Magento\Catalog\Api\Data\ProductInterface|object $entity
+ * @param array $arguments
+ * @return \Magento\Catalog\Api\Data\ProductInterface|object
+ * @throws \Magento\Framework\Exception\InputException
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function execute($entity, $arguments = [])
+ {
+ $attribute = $this->attributeRepository->get('tier_price');
+ $priceRows = $entity->getData($attribute->getName());
+ if (null !== $priceRows) {
+ if (!is_array($priceRows)) {
+ throw new \Magento\Framework\Exception\InputException(
+ __('Tier prices data should be array, but actually other type is received')
+ );
+ }
+ $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);
+
+ // prepare original data to compare
+ $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);
+
+ $delete = array_diff_key($old, $new);
+ $insert = array_diff_key($new, $old);
+ $update = array_intersect_key($new, $old);
+
+ $isAttributeChanged = $this->deleteValues($productId, $delete);
+ $isAttributeChanged |= $this->insertValues($productId, $insert);
+ $isAttributeChanged |= $this->updateValues($update, $old);
+
+ if ($isAttributeChanged) {
+ $valueChangedKey = $attribute->getName() . '_changed';
+ $entity->setData($valueChangedKey, 1);
+ }
+ }
+
+ return $entity;
+ }
+
+ /**
+ * Update existing tier prices for processed product
+ *
+ * @param array $valuesToUpdate
+ * @param array $oldValues
+ * @return bool
+ */
+ private function updateValues(array $valuesToUpdate, array $oldValues): bool
+ {
+ $isChanged = false;
+ foreach ($valuesToUpdate as $key => $value) {
+ if ((!empty($value['value']) && (float)$oldValues[$key]['price'] !== (float)$value['value'])
+ || $this->getPercentage($oldValues[$key]) !== $this->getPercentage($value)
+ ) {
+ $price = new \Magento\Framework\DataObject(
+ [
+ 'value_id' => $oldValues[$key]['price_id'],
+ 'value' => $value['value'],
+ 'percentage_value' => $this->getPercentage($value)
+ ]
+ );
+ $this->tierPriceResource->savePriceData($price);
+ $isChanged = true;
+ }
+ }
+
+ return $isChanged;
+ }
+
+ /**
+ * Insert new tier prices for processed product
+ *
+ * @param int $productId
+ * @param array $valuesToInsert
+ * @return bool
+ */
+ private function insertValues(int $productId, array $valuesToInsert): bool
+ {
+ $isChanged = false;
+ $identifierField = $this->metadataPoll->getMetadata(ProductInterface::class)->getLinkField();
+ foreach ($valuesToInsert as $data) {
+ $price = new \Magento\Framework\DataObject($data);
+ $price->setData(
+ $identifierField,
+ $productId
+ );
+ $this->tierPriceResource->savePriceData($price);
+ $isChanged = true;
+ }
+
+ return $isChanged;
+ }
+
+ /**
+ * Delete tier price values for processed product
+ *
+ * @param int $productId
+ * @param array $valuesToDelete
+ * @return bool
+ */
+ private function deleteValues(int $productId, array $valuesToDelete): bool
+ {
+ $isChanged = false;
+ foreach ($valuesToDelete as $data) {
+ $this->tierPriceResource->deletePriceData($productId, null, $data['price_id']);
+ $isChanged = true;
+ }
+
+ return $isChanged;
+ }
+
+ /**
+ * Get generated price key based on price data
+ *
+ * @param array $priceData
+ * @return string
+ */
+ private function getPriceKey(array $priceData): string
+ {
+ $qty = $this->parseQty($priceData['price_qty']);
+ $key = implode(
+ '-',
+ array_merge([$priceData['website_id'], $priceData['cust_group']], [$qty])
+ );
+
+ return $key;
+ }
+
+ /**
+ * Check by id is website global
+ *
+ * @param int $websiteId
+ * @return bool
+ */
+ private function isWebsiteGlobal(int $websiteId): bool
+ {
+ return $websiteId === 0;
+ }
+
+ /**
+ * Prepare old data to compare.
+ *
+ * @param array|null $origPrices
+ * @return array
+ */
+ private function prepareOldTierPriceToCompare(?array $origPrices): array
+ {
+ $old = [];
+ if (is_array($origPrices)) {
+ foreach ($origPrices as $data) {
+ $key = $this->getPriceKey($data);
+ $old[$key] = $data;
+ }
+ }
+
+ return $old;
+ }
+
+ /**
+ * Prepare new data for save.
+ *
+ * @param array $priceRows
+ * @param bool $isGlobal
+ * @return array
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ private function prepareNewDataForSave(array $priceRows, bool $isGlobal = true): array
+ {
+ $new = [];
+ $priceRows = array_filter($priceRows);
+ foreach ($priceRows as $data) {
+ if (empty($data['delete'])
+ && (!empty($data['price_qty'])
+ || isset($data['cust_group'])
+ || $isGlobal === $this->isWebsiteGlobal((int)$data['website_id']))
+ ) {
+ $key = $this->getPriceKey($data);
+ $new[$key] = $this->prepareTierPrice($data);
+ }
+ }
+
+ return $new;
+ }
+}
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 92b9a2e4239b..db967052cb7a 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,6 +162,7 @@ protected function validatePrice(array $priceRow)
*/
protected function modifyPriceData($object, $data)
{
+ /** @var \Magento\Catalog\Model\Product $object */
$data = parent::modifyPriceData($object, $data);
$price = $object->getPrice();
foreach ($data as $key => $tierPrice) {
@@ -172,6 +176,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/DataProvider.php b/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
index 2bb10d3b31a2..893000544a72 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
@@ -113,27 +113,28 @@ private function customizeAttributeCode($meta)
*/
private function customizeFrontendLabels($meta)
{
+ $labelConfigs = [];
+
foreach ($this->storeRepository->getList() as $store) {
$storeId = $store->getId();
if (!$storeId) {
continue;
}
-
- $meta['manage-titles']['children'] = [
- 'frontend_label[' . $storeId . ']' => $this->arrayManager->set(
- 'arguments/data/config',
- [],
- [
- 'formElement' => Input::NAME,
- 'componentType' => Field::NAME,
- 'label' => $store->getName(),
- 'dataType' => Text::NAME,
- 'dataScope' => 'frontend_label[' . $storeId . ']'
- ]
- ),
- ];
+ $labelConfigs['frontend_label[' . $storeId . ']'] = $this->arrayManager->set(
+ 'arguments/data/config',
+ [],
+ [
+ 'formElement' => Input::NAME,
+ 'componentType' => Field::NAME,
+ 'label' => $store->getName(),
+ 'dataType' => Text::NAME,
+ 'dataScope' => 'frontend_label[' . $storeId . ']'
+ ]
+ );
}
+ $meta['manage-titles']['children'] = $labelConfigs;
+
return $meta;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php b/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
index f2039a5002dc..8b638feafaaf 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/OptionManagement.php
@@ -8,6 +8,9 @@
use Magento\Framework\Exception\InputException;
+/**
+ * Option management model for product attribute.
+ */
class OptionManagement implements \Magento\Catalog\Api\ProductAttributeOptionManagementInterface
{
/**
@@ -25,7 +28,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getItems($attributeCode)
{
@@ -36,7 +39,7 @@ public function getItems($attributeCode)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function add($attributeCode, $option)
{
@@ -47,7 +50,7 @@ public function add($attributeCode, $option)
/** @var \Magento\Eav\Api\Data\AttributeOptionInterface $attributeOption */
$attributeOption = $attributeOption->getLabel();
});
- if (in_array($option->getLabel(), $currentOptions)) {
+ if (in_array($option->getLabel(), $currentOptions, true)) {
return false;
}
}
@@ -59,7 +62,7 @@ public function add($attributeCode, $option)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete($attributeCode, $optionId)
{
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
index f6d3ca36c1e1..99edfe5bc720 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/Attribute/Source/Layout.php b/app/code/Magento/Catalog/Model/Product/Attribute/Source/Layout.php
index 63b1444d1db0..dbc7535dccfa 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Source/Layout.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Source/Layout.php
@@ -17,6 +17,12 @@ class Layout extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource
*/
protected $pageLayoutBuilder;
+ /**
+ * @inheritdoc
+ * @deprecated since the cache is now handled by \Magento\Theme\Model\PageLayout\Config\Builder::$configFiles
+ */
+ protected $_options = null;
+
/**
* @param \Magento\Framework\View\Model\PageLayout\Config\BuilderInterface $pageLayoutBuilder
*/
@@ -26,14 +32,14 @@ public function __construct(\Magento\Framework\View\Model\PageLayout\Config\Buil
}
/**
- * @return array
+ * @inheritdoc
*/
public function getAllOptions()
{
- if (!$this->_options) {
- $this->_options = $this->pageLayoutBuilder->getPageLayoutsConfig()->toOptionArray();
- array_unshift($this->_options, ['value' => '', 'label' => __('No layout updates')]);
- }
- return $this->_options;
+ $options = $this->pageLayoutBuilder->getPageLayoutsConfig()->toOptionArray();
+ array_unshift($options, ['value' => '', 'label' => __('No layout updates')]);
+ $this->_options = $options;
+
+ return $options;
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Compare/Item.php b/app/code/Magento/Catalog/Model/Product/Compare/Item.php
index 3b7e47a46a0a..fd07380bebd7 100644
--- a/app/code/Magento/Catalog/Model/Product/Compare/Item.php
+++ b/app/code/Magento/Catalog/Model/Product/Compare/Item.php
@@ -158,8 +158,8 @@ public function addProductData($product)
{
if ($product instanceof Product) {
$this->setProductId($product->getId());
- } elseif (intval($product)) {
- $this->setProductId(intval($product));
+ } elseif ((int) $product) {
+ $this->setProductId((int) $product);
}
return $this;
diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php
index e94104ae473a..44ebdf0f1f28 100644
--- a/app/code/Magento/Catalog/Model/Product/Copier.php
+++ b/app/code/Magento/Catalog/Model/Product/Copier.php
@@ -1,14 +1,20 @@
getWebsiteIds();
$product->getCategoryIds();
@@ -70,21 +76,9 @@ public function copy(\Magento\Catalog\Model\Product $product)
$duplicate->setUpdatedAt(null);
$duplicate->setId(null);
$duplicate->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID);
-
$this->copyConstructor->build($product, $duplicate);
- $isDuplicateSaved = false;
- do {
- $urlKey = $duplicate->getUrlKey();
- $urlKey = preg_match('/(.*)-(\d+)$/', $urlKey, $matches)
- ? $matches[1] . '-' . ($matches[2] + 1)
- : $urlKey . '-1';
- $duplicate->setUrlKey($urlKey);
- try {
- $duplicate->save();
- $isDuplicateSaved = true;
- } catch (\Magento\Framework\Exception\AlreadyExistsException $e) {
- }
- } while (!$isDuplicateSaved);
+ $this->setDefaultUrl($product, $duplicate);
+ $this->setStoresUrl($product, $duplicate);
$this->getOptionRepository()->duplicate($product, $duplicate);
$product->getResource()->duplicate(
$product->getData($metadata->getLinkField()),
@@ -94,6 +88,83 @@ public function copy(\Magento\Catalog\Model\Product $product)
}
/**
+ * Set default URL.
+ *
+ * @param Product $product
+ * @param Product $duplicate
+ * @return void
+ */
+ private function setDefaultUrl(Product $product, Product $duplicate) : void
+ {
+ $duplicate->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID);
+ $resource = $product->getResource();
+ $attribute = $resource->getAttribute('url_key');
+ $productId = $product->getId();
+ $urlKey = $resource->getAttributeRawValue($productId, 'url_key', \Magento\Store\Model\Store::DEFAULT_STORE_ID);
+ do {
+ $urlKey = $this->modifyUrl($urlKey);
+ $duplicate->setUrlKey($urlKey);
+ } while (!$attribute->getEntity()->checkAttributeUniqueValue($attribute, $duplicate));
+ $duplicate->setData('url_path', null);
+ $duplicate->save();
+ }
+
+ /**
+ * Set URL for each store.
+ *
+ * @param Product $product
+ * @param Product $duplicate
+ * @return void
+ */
+ private function setStoresUrl(Product $product, Product $duplicate) : void
+ {
+ $storeIds = $duplicate->getStoreIds();
+ $productId = $product->getId();
+ $productResource = $product->getResource();
+ $defaultUrlKey = $productResource->getAttributeRawValue(
+ $productId,
+ 'url_key',
+ \Magento\Store\Model\Store::DEFAULT_STORE_ID
+ );
+ $duplicate->setData('save_rewrites_history', false);
+ foreach ($storeIds as $storeId) {
+ $isDuplicateSaved = false;
+ $duplicate->setStoreId($storeId);
+ $urlKey = $productResource->getAttributeRawValue($productId, 'url_key', $storeId);
+ if ($urlKey === $defaultUrlKey) {
+ continue;
+ }
+ do {
+ $urlKey = $this->modifyUrl($urlKey);
+ $duplicate->setUrlKey($urlKey);
+ $duplicate->setData('url_path', null);
+ try {
+ $duplicate->save();
+ $isDuplicateSaved = true;
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock
+ } catch (\Magento\Framework\Exception\AlreadyExistsException $e) {
+ }
+ } while (!$isDuplicateSaved);
+ }
+ $duplicate->setStoreId(\Magento\Store\Model\Store::DEFAULT_STORE_ID);
+ }
+
+ /**
+ * Modify URL key.
+ *
+ * @param string $urlKey
+ * @return string
+ */
+ private function modifyUrl(string $urlKey) : string
+ {
+ return preg_match('/(.*)-(\d+)$/', $urlKey, $matches)
+ ? $matches[1] . '-' . ($matches[2] + 1)
+ : $urlKey . '-1';
+ }
+
+ /**
+ * Returns product option repository.
+ *
* @return Option\Repository
* @deprecated 101.0.0
*/
@@ -107,6 +178,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/CreateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php
index bd28b65bb798..e06e85e90a2d 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/CreateHandler.php
@@ -102,6 +102,8 @@ public function __construct(
}
/**
+ * Execute create handler
+ *
* @param object $product
* @param array $arguments
* @return object
@@ -167,23 +169,19 @@ public function execute($product, $arguments = [])
if (empty($attrData) && empty($clearImages) && empty($newImages) && empty($existImages)) {
continue;
}
- if (in_array($attrData, $clearImages)) {
- $product->setData($mediaAttrCode, 'no_selection');
- }
-
- if (in_array($attrData, array_keys($newImages))) {
- $product->setData($mediaAttrCode, $newImages[$attrData]['new_file']);
- $product->setData($mediaAttrCode . '_label', $newImages[$attrData]['label']);
- }
-
- if (in_array($attrData, array_keys($existImages)) && isset($existImages[$attrData]['label'])) {
- $product->setData($mediaAttrCode . '_label', $existImages[$attrData]['label']);
- }
- if (!empty($product->getData($mediaAttrCode))) {
- $product->addAttributeUpdate(
+ $this->processMediaAttribute(
+ $product,
+ $mediaAttrCode,
+ $clearImages,
+ $newImages
+ );
+ if (in_array($mediaAttrCode, ['image', 'small_image', 'thumbnail'])) {
+ $this->processMediaAttributeLabel(
+ $product,
$mediaAttrCode,
- $product->getData($mediaAttrCode),
- $product->getStoreId()
+ $clearImages,
+ $newImages,
+ $existImages
);
}
}
@@ -208,6 +206,8 @@ public function execute($product, $arguments = [])
}
/**
+ * Returns media gallery attribute instance
+ *
* @return \Magento\Catalog\Api\Data\ProductAttributeInterface
* @since 101.0.0
*/
@@ -223,17 +223,22 @@ public function getAttribute()
}
/**
+ * Process delete images
+ *
* @param \Magento\Catalog\Model\Product $product
* @param array $images
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @since 101.0.0
+ * phpcs:disable Magento2.CodeAnalysis.EmptyBlock
*/
protected function processDeletedImages($product, array &$images)
{
}
/**
+ * Process images
+ *
* @param \Magento\Catalog\Model\Product $product
* @param array $images
* @return void
@@ -296,6 +301,8 @@ protected function processNewImage($product, array &$image)
}
/**
+ * Duplicate attribute
+ *
* @param \Magento\Catalog\Model\Product $product
* @return $this
* @since 101.0.0
@@ -312,7 +319,7 @@ protected function duplicate($product)
$this->resourceModel->duplicate(
$this->getAttribute()->getAttributeId(),
- isset($mediaGalleryData['duplicate']) ? $mediaGalleryData['duplicate'] : [],
+ $mediaGalleryData['duplicate'] ?? [],
$product->getOriginalLinkId(),
$product->getData($this->metadata->getLinkField())
);
@@ -364,6 +371,8 @@ private function getSafeFilename($file)
}
/**
+ * Returns file name according to tmp name
+ *
* @param string $file
* @return string
* @since 101.0.0
@@ -392,6 +401,7 @@ protected function getUniqueFileName($file, $forTmp = false)
$destinationFile = $forTmp
? $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getTmpMediaPath($file))
: $this->mediaDirectory->getAbsolutePath($this->mediaConfig->getMediaPath($file));
+ // phpcs:disable Magento2.Functions.DiscouragedFunction
$destFile = dirname($file) . '/' . FileUploader::getNewFileName($destinationFile);
}
@@ -412,6 +422,7 @@ protected function copyImage($file)
$destinationFile = $this->getUniqueFileName($file);
if (!$this->mediaDirectory->isFile($this->mediaConfig->getMediaPath($file))) {
+ // phpcs:ignore Magento2.Exceptions.DirectThrow
throw new \Exception();
}
@@ -429,6 +440,7 @@ protected function copyImage($file)
}
return str_replace('\\', '/', $destinationFile);
+ // phpcs:ignore Magento2.Exceptions.ThrowCatch
} catch (\Exception $e) {
$file = $this->mediaConfig->getMediaPath($file);
throw new \Magento\Framework\Exception\LocalizedException(
@@ -449,4 +461,81 @@ private function getMediaAttributeCodes()
}
return $this->mediaAttributeCodes;
}
+
+ /**
+ * Process media attribute
+ *
+ * @param \Magento\Catalog\Model\Product $product
+ * @param string $mediaAttrCode
+ * @param array $clearImages
+ * @param array $newImages
+ */
+ private function processMediaAttribute(
+ \Magento\Catalog\Model\Product $product,
+ $mediaAttrCode,
+ array $clearImages,
+ array $newImages
+ ) {
+ $attrData = $product->getData($mediaAttrCode);
+ if (in_array($attrData, $clearImages)) {
+ $product->setData($mediaAttrCode, 'no_selection');
+ }
+
+ if (in_array($attrData, array_keys($newImages))) {
+ $product->setData($mediaAttrCode, $newImages[$attrData]['new_file']);
+ }
+ if (!empty($product->getData($mediaAttrCode))) {
+ $product->addAttributeUpdate(
+ $mediaAttrCode,
+ $product->getData($mediaAttrCode),
+ $product->getStoreId()
+ );
+ }
+ }
+
+ /**
+ * Process media attribute label
+ *
+ * @param \Magento\Catalog\Model\Product $product
+ * @param string $mediaAttrCode
+ * @param array $clearImages
+ * @param array $newImages
+ * @param array $existImages
+ */
+ private function processMediaAttributeLabel(
+ \Magento\Catalog\Model\Product $product,
+ $mediaAttrCode,
+ array $clearImages,
+ array $newImages,
+ array $existImages
+ ) {
+ $resetLabel = false;
+ $attrData = $product->getData($mediaAttrCode);
+ if (in_array($attrData, $clearImages)) {
+ $product->setData($mediaAttrCode . '_label', null);
+ $resetLabel = true;
+ }
+
+ if (in_array($attrData, array_keys($newImages))) {
+ $product->setData($mediaAttrCode . '_label', $newImages[$attrData]['label']);
+ }
+
+ if (in_array($attrData, array_keys($existImages)) && isset($existImages[$attrData]['label'])) {
+ $product->setData($mediaAttrCode . '_label', $existImages[$attrData]['label']);
+ }
+
+ if ($attrData === 'no_selection' && !empty($product->getData($mediaAttrCode . '_label'))) {
+ $product->setData($mediaAttrCode . '_label', null);
+ $resetLabel = true;
+ }
+ if (!empty($product->getData($mediaAttrCode . '_label'))
+ || $resetLabel === true
+ ) {
+ $product->addAttributeUpdate(
+ $mediaAttrCode . '_label',
+ $product->getData($mediaAttrCode . '_label'),
+ $product->getStoreId()
+ );
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
index 4d274a071d08..c993e51c8bc0 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 = [];
@@ -69,8 +71,10 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry)
$product->setMediaGalleryEntries($existingMediaGalleryEntries);
try {
$product = $this->productRepository->save($product);
+ // phpcs:ignore Magento2.Exceptions.ThrowCatch
} catch (InputException $inputException) {
throw $inputException;
+ // phpcs:ignore Magento2.Exceptions.ThrowCatch
} catch (\Exception $e) {
throw new StateException(__("The product can't be saved."));
}
@@ -84,11 +88,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(
@@ -103,7 +107,10 @@ public function update($sku, ProductAttributeMediaGalleryEntryInterface $entry)
if ($existingEntry->getId() == $entry->getId()) {
$found = true;
- if ($entry->getFile()) {
+
+ $file = $entry->getContent();
+
+ if ($file && $file->getBase64EncodedData() || $entry->getFile()) {
$entry->setId(null);
}
$existingMediaGalleryEntries[$key] = $entry;
@@ -125,11 +132,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 +162,7 @@ public function remove($sku, $entryId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($sku, $entryId)
{
@@ -176,7 +183,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 c6c7fbda7e9e..091232474536 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 c785d08e64b7..a3726207b302 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/Gallery/UpdateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php
index 8ad8dcb4812e..189135776b68 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php
@@ -16,7 +16,8 @@
class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler
{
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
protected function processDeletedImages($product, array &$images)
@@ -31,7 +32,7 @@ protected function processDeletedImages($product, array &$images)
foreach ($images as &$image) {
if (!empty($image['removed'])) {
- if (!empty($image['value_id']) && !isset($picturesInOtherStores[$image['file']])) {
+ if (!empty($image['value_id'])) {
if (preg_match('/\.\.(\\\|\/)/', $image['file'])) {
continue;
}
@@ -52,7 +53,8 @@ protected function processDeletedImages($product, array &$images)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
protected function processNewImage($product, array &$image)
@@ -79,6 +81,8 @@ protected function processNewImage($product, array &$image)
}
/**
+ * Retrieve store ids from product.
+ *
* @param \Magento\Catalog\Model\Product $product
* @return array
* @since 101.0.0
@@ -97,6 +101,8 @@ protected function extractStoreIds($product)
}
/**
+ * Remove deleted images.
+ *
* @param array $files
* @return null
* @since 101.0.0
diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php
index 09ba68ddbe2b..a0be36c5a327 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,12 +520,14 @@ public function resize()
}
/**
+ * Rotate image
+ *
* @param int $angle
* @return $this
*/
public function rotate($angle)
{
- $angle = intval($angle);
+ $angle = (int) $angle;
$this->getImageProcessor()->rotate($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 dd8d352feceb..4a55714a27ec 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,13 +156,12 @@ private function getWatermark(string $type): array
/**
* Get frame from product_image_white_borders
+ *
* @return bool
*/
private function hasDefaultFrame(): bool
{
- return (bool) $this->viewConfig->getViewConfig()->getVarValue(
- 'Magento_Catalog',
- 'product_image_white_borders'
- );
+ return (bool) $this->viewConfig->getViewConfig(['area' => \Magento\Framework\App\Area::AREA_FRONTEND])
+ ->getVarValue('Magento_Catalog', 'product_image_white_borders');
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Media/Config.php b/app/code/Magento/Catalog/Model/Product/Media/Config.php
index 72936d317399..33af93db13b4 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.php b/app/code/Magento/Catalog/Model/Product/Option.php
index acfc454883e1..b4a4ec08d390 100644
--- a/app/code/Magento/Catalog/Model/Product/Option.php
+++ b/app/code/Magento/Catalog/Model/Product/Option.php
@@ -323,7 +323,7 @@ public function getGroupByType($type = null)
self::OPTION_TYPE_TIME => self::OPTION_GROUP_DATE,
];
- return isset($optionGroupsToTypes[$type]) ? $optionGroupsToTypes[$type] : '';
+ return $optionGroupsToTypes[$type] ?? '';
}
/**
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Repository.php b/app/code/Magento/Catalog/Model/Product/Option/Repository.php
index 9dc9695daffd..bb4e247de32d 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Repository.php
@@ -14,6 +14,8 @@
use Magento\Framework\App\ObjectManager;
/**
+ * Product custom options repository
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Repository implements \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface
@@ -83,7 +85,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList($sku)
{
@@ -92,7 +94,7 @@ public function getList($sku)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getProductOptions(ProductInterface $product, $requiredOnly = false)
{
@@ -104,7 +106,7 @@ public function getProductOptions(ProductInterface $product, $requiredOnly = fal
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($sku, $optionId)
{
@@ -117,7 +119,7 @@ public function get($sku, $optionId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $entity)
{
@@ -126,7 +128,7 @@ public function delete(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $e
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function duplicate(
\Magento\Catalog\Api\Data\ProductInterface $product,
@@ -142,7 +144,7 @@ public function duplicate(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $option)
{
@@ -184,7 +186,7 @@ public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $opt
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteByIdentifier($sku, $optionId)
{
@@ -209,8 +211,8 @@ public function deleteByIdentifier($sku, $optionId)
/**
* Mark original values for removal if they are absent among new values
*
- * @param $newValues array
- * @param $originalValues \Magento\Catalog\Model\Product\Option\Value[]
+ * @param array $newValues
+ * @param \Magento\Catalog\Model\Product\Option\Value[] $originalValues
* @return array
*/
protected function markRemovedValues($newValues, $originalValues)
@@ -234,6 +236,8 @@ protected function markRemovedValues($newValues, $originalValues)
}
/**
+ * Get hydrator pool
+ *
* @return \Magento\Framework\EntityManager\HydratorPool
* @deprecated 101.0.0
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
index c4a2d60414a7..9cb6cda4d0a0 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Product\Option;
use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface as OptionRepository;
@@ -28,6 +30,8 @@ public function __construct(
}
/**
+ * Perform action on relation/extension attribute
+ *
* @param object $entity
* @param array $arguments
* @return \Magento\Catalog\Api\Data\ProductInterface|object
@@ -35,6 +39,10 @@ public function __construct(
*/
public function execute($entity, $arguments = [])
{
+ if ($entity->getOptionsSaved()) {
+ return $entity;
+ }
+
$options = $entity->getOptions();
$optionIds = [];
@@ -52,11 +60,26 @@ public function execute($entity, $arguments = [])
}
}
if ($options) {
- foreach ($options as $option) {
- $this->optionRepository->save($option);
- }
+ $this->processOptionsSaving($options, (bool)$entity->dataHasChangedFor('sku'), (string)$entity->getSku());
}
return $entity;
}
+
+ /**
+ * Save custom options
+ *
+ * @param array $options
+ * @param bool $hasChangedSku
+ * @param string $newSku
+ */
+ private function processOptionsSaving(array $options, bool $hasChangedSku, string $newSku)
+ {
+ foreach ($options as $option) {
+ if ($hasChangedSku && $option->hasData('product_sku')) {
+ $option->setProductSku($newSku);
+ }
+ $this->optionRepository->save($option);
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php
index 7517459da650..2b4739ebeb73 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php
@@ -12,6 +12,7 @@
* Catalog product option date type
*
* @author Magento Core Team
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class Date extends \Magento\Catalog\Model\Product\Option\Type\DefaultType
{
@@ -102,11 +103,11 @@ public function validateUserValue($values)
$this->setUserValue(
[
'date' => isset($value['date']) ? $value['date'] : '',
- 'year' => isset($value['year']) ? intval($value['year']) : 0,
- 'month' => isset($value['month']) ? intval($value['month']) : 0,
- 'day' => isset($value['day']) ? intval($value['day']) : 0,
- 'hour' => isset($value['hour']) ? intval($value['hour']) : 0,
- 'minute' => isset($value['minute']) ? intval($value['minute']) : 0,
+ 'year' => isset($value['year']) ? (int) $value['year'] : 0,
+ 'month' => isset($value['month']) ? (int) $value['month'] : 0,
+ 'day' => isset($value['day']) ? (int) $value['day'] : 0,
+ 'hour' => isset($value['hour']) ? (int) $value['hour'] : 0,
+ 'minute' => isset($value['minute']) ? (int) $value['minute'] : 0,
'day_part' => isset($value['day_part']) ? $value['day_part'] : '',
'date_internal' => isset($value['date_internal']) ? $value['date_internal'] : '',
]
@@ -147,7 +148,6 @@ public function validateUserValue($values)
public function prepareForCart()
{
if ($this->getIsValid() && $this->getUserValue() !== null) {
- $option = $this->getOption();
$value = $this->getUserValue();
if (isset($value['date_internal']) && $value['date_internal'] != '') {
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php b/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php
index 2390a049fbeb..c388be8b6f39 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/DefaultType.php
@@ -279,7 +279,7 @@ public function getFormattedOptionValue($optionValue)
*/
public function getCustomizedView($optionInfo)
{
- return isset($optionInfo['value']) ? $optionInfo['value'] : $optionInfo;
+ return $optionInfo['value'] ?? $optionInfo;
}
/**
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 000000000000..c9afdf023b30
--- /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 32c901afe8e7..a7add0ad87b8 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 d6a5cb1cbc29..fef4999a1174 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 37e4c7b310a8..100ad37273cf 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/Option/Type/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
index 4a257a478106..d88dd5836289 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Select.php
@@ -71,7 +71,7 @@ public function validateUserValue($values)
}
if (!$this->_isSingleSelection()) {
$valuesCollection = $option->getOptionValuesByOptionId($value, $this->getProduct()->getStoreId())->load();
- $valueCount = is_array($value) ? count($value) : 1;
+ $valueCount = is_array($value) ? count($value) : 0;
if ($valuesCollection->count() != $valueCount) {
$this->setIsValid(false);
throw new LocalizedException(
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
index ee508e30cc93..08455430ccac 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
@@ -9,6 +9,9 @@
use Magento\Catalog\Model\Product\Option;
use Zend_Validate_Exception;
+/**
+ * Product option default validator
+ */
class DefaultValidator extends \Magento\Framework\Validator\AbstractValidator
{
/**
@@ -25,13 +28,20 @@ class DefaultValidator extends \Magento\Framework\Validator\AbstractValidator
*/
protected $priceTypes;
+ /**
+ * @var \Magento\Framework\Locale\FormatInterface
+ */
+ private $localeFormat;
+
/**
* @param \Magento\Catalog\Model\ProductOptions\ConfigInterface $productOptionConfig
* @param \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig
+ * @param \Magento\Framework\Locale\FormatInterface|null $localeFormat
*/
public function __construct(
\Magento\Catalog\Model\ProductOptions\ConfigInterface $productOptionConfig,
- \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig
+ \Magento\Catalog\Model\Config\Source\Product\Options\Price $priceConfig,
+ \Magento\Framework\Locale\FormatInterface $localeFormat = null
) {
foreach ($productOptionConfig->getAll() as $option) {
foreach ($option['types'] as $type) {
@@ -42,6 +52,9 @@ public function __construct(
foreach ($priceConfig->toOptionArray() as $item) {
$this->priceTypes[] = $item['value'];
}
+
+ $this->localeFormat = $localeFormat ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\Framework\Locale\FormatInterface::class);
}
/**
@@ -134,11 +147,11 @@ protected function validateOptionType(Option $option)
*/
protected function validateOptionValue(Option $option)
{
- return $this->isInRange($option->getPriceType(), $this->priceTypes);
+ return $this->isInRange($option->getPriceType(), $this->priceTypes) && $this->isNumber($option->getPrice());
}
/**
- * Check whether value is empty
+ * Check whether the value is empty
*
* @param mixed $value
* @return bool
@@ -149,7 +162,7 @@ protected function isEmpty($value)
}
/**
- * Check whether value is in range
+ * Check whether the value is in range
*
* @param string $value
* @param array $range
@@ -161,13 +174,24 @@ protected function isInRange($value, array $range)
}
/**
- * Check whether value is not negative
+ * Check whether the value is negative
*
* @param string $value
* @return bool
*/
protected function isNegative($value)
{
- return intval($value) < 0;
+ return $this->localeFormat->getNumber($value) < 0;
+ }
+
+ /**
+ * Check whether the value is a number
+ *
+ * @param string $value
+ * @return bool
+ */
+ public function isNumber($value)
+ {
+ return is_numeric($this->localeFormat->getNumber($value));
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Pool.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Pool.php
index 1e0065424955..2256f031098f 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Pool.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Pool.php
@@ -29,6 +29,6 @@ public function __construct(array $validators)
*/
public function get($type)
{
- return isset($this->validators[$type]) ? $this->validators[$type] : $this->validators['default'];
+ return $this->validators[$type] ?? $this->validators['default'];
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php
index 44756890b6ed..209531f59981 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php
@@ -8,6 +8,9 @@
use Magento\Catalog\Model\Product\Option;
+/**
+ * Select validator class
+ */
class Select extends DefaultValidator
{
/**
@@ -83,7 +86,7 @@ protected function isValidOptionPrice($priceType, $price, $storeId)
if (!$priceType && !$price) {
return true;
}
- if (!$this->isInRange($priceType, $this->priceTypes)) {
+ if (!$this->isInRange($priceType, $this->priceTypes) || !$this->isNumber($price)) {
return false;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
index 1bddd2d07cd8..3ee064670a46 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceStorage.php
@@ -97,7 +97,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get(array $skus)
{
@@ -107,7 +107,7 @@ public function get(array $skus)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function update(array $prices)
{
@@ -128,7 +128,7 @@ public function update(array $prices)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function replace(array $prices)
{
@@ -144,7 +144,7 @@ public function replace(array $prices)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(array $prices)
{
@@ -171,16 +171,17 @@ private function getExistingPrices(array $skus, $groupBySku = false)
$ids = $this->retrieveAffectedIds($skus);
$rawPrices = $this->tierPricePersistence->get($ids);
$prices = [];
-
- $linkField = $this->tierPricePersistence->getEntityLinkField();
- $skuByIdLookup = $this->buildSkuByIdLookup($skus);
- foreach ($rawPrices as $rawPrice) {
- $sku = $skuByIdLookup[$rawPrice[$linkField]];
- $price = $this->tierPriceFactory->create($rawPrice, $sku);
- if ($groupBySku) {
- $prices[$sku][] = $price;
- } else {
- $prices[] = $price;
+ if ($rawPrices) {
+ $linkField = $this->tierPricePersistence->getEntityLinkField();
+ $skuByIdLookup = $this->buildSkuByIdLookup($skus);
+ foreach ($rawPrices as $rawPrice) {
+ $sku = $skuByIdLookup[$rawPrice[$linkField]];
+ $price = $this->tierPriceFactory->create($rawPrice, $sku);
+ if ($groupBySku) {
+ $prices[$sku][] = $price;
+ } else {
+ $prices[] = $price;
+ }
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/PriceModifier.php b/app/code/Magento/Catalog/Model/Product/PriceModifier.php
index 48d53b461452..c4d5bdfedcd5 100644
--- a/app/code/Magento/Catalog/Model/Product/PriceModifier.php
+++ b/app/code/Magento/Catalog/Model/Product/PriceModifier.php
@@ -9,6 +9,9 @@
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
+/**
+ * Product form price modifier
+ */
class PriceModifier
{
/**
@@ -26,6 +29,8 @@ public function __construct(
}
/**
+ * Remove tier price
+ *
* @param \Magento\Catalog\Model\Product $product
* @param int|string $customerGroupId
* @param int $qty
@@ -46,11 +51,11 @@ public function removeTierPrice(\Magento\Catalog\Model\Product $product, $custom
foreach ($prices as $key => $tierPrice) {
if ($customerGroupId == 'all' && $tierPrice['price_qty'] == $qty
- && $tierPrice['all_groups'] == 1 && intval($tierPrice['website_id']) === intval($websiteId)
+ && $tierPrice['all_groups'] == 1 && (int) $tierPrice['website_id'] === (int) $websiteId
) {
unset($prices[$key]);
} elseif ($tierPrice['price_qty'] == $qty && $tierPrice['cust_group'] == $customerGroupId
- && intval($tierPrice['website_id']) === intval($websiteId)
+ && (int) $tierPrice['website_id'] === (int) $websiteId
) {
unset($prices[$key]);
}
diff --git a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php
index 3ec8e968aa24..24775a791e59 100644
--- a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php
+++ b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php
@@ -16,6 +16,8 @@
use Magento\Framework\EntityManager\EntityManager;
/**
+ * A Product Widget Synchronizer.
+ *
* Service which allows to sync product widget information, such as product id with db. In order to reuse this info
* on different devices
*/
@@ -85,9 +87,10 @@ public function __construct(
}
/**
- * Find lifetime in configuration. Configuration is hold in Stores Configuration
- * Also this configuration is generated by:
- * @see \Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration
+ * Finds lifetime in configuration.
+ *
+ * Configuration is hold in Stores Configuration. Also this configuration is generated by
+ * {@see Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration}
*
* @param string $namespace
* @return int
@@ -108,6 +111,8 @@ private function getLifeTimeByNamespace($namespace)
}
/**
+ * Filters actions.
+ *
* In order to avoid suspicious actions, we need to filter them in DESC order, and slice only items that
* can be persisted in database.
*
@@ -138,7 +143,9 @@ private function getProductIdsByActions(array $actions)
$productIds = [];
foreach ($actions as $action) {
- $productIds[] = $action['product_id'];
+ if (isset($action['product_id'])) {
+ $productIds[] = $action['product_id'];
+ }
}
return $productIds;
@@ -159,33 +166,37 @@ public function syncActions(array $productsData, $typeId)
$customerId = $this->session->getCustomerId();
$visitorId = $this->visitor->getId();
$collection = $this->getActionsByType($typeId);
- $collection->addFieldToFilter('product_id', $this->getProductIdsByActions($productsData));
-
- /**
- * Note that collection is also filtered by visitor id and customer id
- * This collection shouldn't be flushed when visitor has products and then login
- * It can remove only products for visitor, or only products for customer
- *
- * ['product_id' => 'added_at']
- * @var ProductFrontendActionInterface $item
- */
- foreach ($collection as $item) {
- $this->entityManager->delete($item);
- }
-
- foreach ($productsData as $productId => $productData) {
- /** @var ProductFrontendActionInterface $action */
- $action = $this->productFrontendActionFactory->create([
- 'data' => [
- 'visitor_id' => $customerId ? null : $visitorId,
- 'customer_id' => $this->session->getCustomerId(),
- 'added_at' => $productData['added_at'],
- 'product_id' => $productId,
- 'type_id' => $typeId
- ]
- ]);
-
- $this->entityManager->save($action);
+ $productIds = $this->getProductIdsByActions($productsData);
+
+ if ($productIds) {
+ $collection->addFieldToFilter('product_id', $productIds);
+
+ /**
+ * Note that collection is also filtered by visitor id and customer id
+ * This collection shouldn't be flushed when visitor has products and then login
+ * It can remove only products for visitor, or only products for customer
+ *
+ * ['product_id' => 'added_at']
+ * @var ProductFrontendActionInterface $item
+ */
+ foreach ($collection as $item) {
+ $this->entityManager->delete($item);
+ }
+
+ foreach ($productsData as $productId => $productData) {
+ /** @var ProductFrontendActionInterface $action */
+ $action = $this->productFrontendActionFactory->create([
+ 'data' => [
+ 'visitor_id' => $customerId ? null : $visitorId,
+ 'customer_id' => $this->session->getCustomerId(),
+ 'added_at' => $productData['added_at'],
+ 'product_id' => $productId,
+ 'type_id' => $typeId
+ ]
+ ]);
+
+ $this->entityManager->save($action);
+ }
}
}
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 000000000000..9c1a781d594f
--- /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/Product/TierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
index 822959bfc851..f2da1e770279 100644
--- a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php
@@ -15,6 +15,8 @@
use Magento\Framework\Exception\TemporaryStateExceptionInterface;
/**
+ * Product tier price management
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class TierPriceManagement implements \Magento\Catalog\Api\ProductTierPriceManagementInterface
@@ -82,7 +84,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
@@ -148,7 +150,7 @@ public function add($sku, $customerGroupId, $price, $qty)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function remove($sku, $customerGroupId, $qty)
{
@@ -163,7 +165,7 @@ public function remove($sku, $customerGroupId, $qty)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList($sku, $customerGroupId)
{
@@ -181,7 +183,7 @@ public function getList($sku, $customerGroupId)
$prices = [];
foreach ($product->getData('tier_price') as $price) {
- if ((is_numeric($customerGroupId) && intval($price['cust_group']) === intval($customerGroupId))
+ if ((is_numeric($customerGroupId) && (int) $price['cust_group'] === (int) $customerGroupId)
|| ($customerGroupId === 'all' && $price['all_groups'])
) {
/** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */
diff --git a/app/code/Magento/Catalog/Model/Product/Type.php b/app/code/Magento/Catalog/Model/Product/Type.php
index dc3971397acb..4c973be20dee 100644
--- a/app/code/Magento/Catalog/Model/Product/Type.php
+++ b/app/code/Magento/Catalog/Model/Product/Type.php
@@ -232,7 +232,7 @@ public function getOptions()
public function getOptionText($optionId)
{
$options = $this->getOptionArray();
- return isset($options[$optionId]) ? $options[$optionId] : null;
+ return $options[$optionId] ?? null;
}
/**
@@ -285,7 +285,7 @@ public function getTypesByPriority()
$types = $this->getTypes();
foreach ($types as $typeId => $typeInfo) {
- $priority = isset($typeInfo['index_priority']) ? abs(intval($typeInfo['index_priority'])) : 0;
+ $priority = isset($typeInfo['index_priority']) ? abs((int) $typeInfo['index_priority']) : 0;
if (!empty($typeInfo['composite'])) {
$compositePriority[$typeId] = $priority;
} else {
@@ -307,7 +307,7 @@ public function getTypesByPriority()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function toOptionArray()
{
diff --git a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
index 1b5cf37f6cbb..e6804d9246fa 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
@@ -935,7 +935,7 @@ public function getForceChildItemQtyChanges($product)
*/
public function prepareQuoteItemQty($qty, $product)
{
- return floatval($qty);
+ return (float)$qty;
}
/**
diff --git a/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php
new file mode 100644
index 000000000000..dabfdb74f011
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php
@@ -0,0 +1,133 @@
+specialPrice = $specialPrice;
+ parent::__construct(
+ $ruleFactory,
+ $storeManager,
+ $localeDate,
+ $customerSession,
+ $eventManager,
+ $priceCurrency,
+ $groupManagement,
+ $tierPriceFactory,
+ $config
+ );
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @deprecated
+ */
+ protected function _applySpecialPrice($product, $finalPrice)
+ {
+ if (!$product->getSpecialPrice()) {
+ return $finalPrice;
+ }
+
+ $specialPrices = $this->getSpecialPrices($product);
+ $specialPrice = !(empty($specialPrices)) ? min($specialPrices) : $product->getSpecialPrice();
+
+ $specialPrice = $this->calculateSpecialPrice(
+ $finalPrice,
+ $specialPrice,
+ $product->getSpecialFromDate(),
+ $product->getSpecialToDate(),
+ WebsiteInterface::ADMIN_CODE
+ );
+ $product->setData('special_price', $specialPrice);
+
+ return $specialPrice;
+ }
+
+ /**
+ * Get special prices.
+ *
+ * @param mixed $product
+ * @return array
+ */
+ private function getSpecialPrices($product): array
+ {
+ $allSpecialPrices = $this->specialPrice->get([$product->getSku()]);
+ $specialPrices = [];
+ foreach ($allSpecialPrices as $price) {
+ if ($this->isSuitableSpecialPrice($product, $price)) {
+ $specialPrices[] = $price['value'];
+ }
+ }
+
+ return $specialPrices;
+ }
+
+ /**
+ * Price is suitable from default and current store + start and end date are equal.
+ *
+ * @param mixed $product
+ * @param array $price
+ * @return bool
+ */
+ private function isSuitableSpecialPrice($product, array $price): bool
+ {
+ $priceStoreId = $price[Store::STORE_ID];
+ if (($priceStoreId == Store::DEFAULT_STORE_ID || $product->getStoreId() == $priceStoreId)
+ && $price[SpecialPriceInterface::PRICE_FROM] == $product->getSpecialFromDate()
+ && $price[SpecialPriceInterface::PRICE_TO] == $product->getSpecialToDate()) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php
index f6caa299d66d..b30624b79dd5 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/Price.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php
@@ -11,12 +11,14 @@
use Magento\Store\Model\Store;
use Magento\Catalog\Api\Data\ProductTierPriceExtensionFactory;
use Magento\Framework\App\ObjectManager;
+use Magento\Store\Api\Data\WebsiteInterface;
/**
* Product type price model
*
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @since 100.0.2
*/
class Price
@@ -184,6 +186,8 @@ public function getFinalPrice($qty, $product)
}
/**
+ * Retrieve final price for child product
+ *
* @param Product $product
* @param float $productQty
* @param Product $childProduct
@@ -428,6 +432,8 @@ public function setTierPrices($product, array $tierPrices = null)
}
/**
+ * Retrieve customer group id from product
+ *
* @param Product $product
* @return int
*/
@@ -453,7 +459,7 @@ protected function _applySpecialPrice($product, $finalPrice)
$product->getSpecialPrice(),
$product->getSpecialFromDate(),
$product->getSpecialToDate(),
- $product->getStore()
+ WebsiteInterface::ADMIN_CODE
);
}
@@ -601,7 +607,7 @@ public function calculatePrice(
$specialPrice,
$specialPriceFrom,
$specialPriceTo,
- $sId
+ WebsiteInterface::ADMIN_CODE
);
if ($rulePrice === false) {
diff --git a/app/code/Magento/Catalog/Model/Product/Url.php b/app/code/Magento/Catalog/Model/Product/Url.php
index c291dc33feda..f3ac9f55d1ae 100644
--- a/app/code/Magento/Catalog/Model/Product/Url.php
+++ b/app/code/Magento/Catalog/Model/Product/Url.php
@@ -7,6 +7,7 @@
use Magento\UrlRewrite\Model\UrlFinderInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\Framework\App\Config\ScopeConfigInterface;
/**
* Product Url model
@@ -45,6 +46,11 @@ class Url extends \Magento\Framework\DataObject
*/
protected $urlFinder;
+ /**
+ * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ */
+ private $scopeConfig;
+
/**
* @param \Magento\Framework\UrlFactory $urlFactory
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -52,6 +58,7 @@ class Url extends \Magento\Framework\DataObject
* @param \Magento\Framework\Session\SidResolverInterface $sidResolver
* @param UrlFinderInterface $urlFinder
* @param array $data
+ * @param ScopeConfigInterface|null $scopeConfig
*/
public function __construct(
\Magento\Framework\UrlFactory $urlFactory,
@@ -59,7 +66,8 @@ public function __construct(
\Magento\Framework\Filter\FilterManager $filter,
\Magento\Framework\Session\SidResolverInterface $sidResolver,
UrlFinderInterface $urlFinder,
- array $data = []
+ array $data = [],
+ ScopeConfigInterface $scopeConfig = null
) {
parent::__construct($data);
$this->urlFactory = $urlFactory;
@@ -67,16 +75,8 @@ public function __construct(
$this->filter = $filter;
$this->sidResolver = $sidResolver;
$this->urlFinder = $urlFinder;
- }
-
- /**
- * Retrieve URL Instance
- *
- * @return \Magento\Framework\UrlInterface
- */
- private function getUrlInstance()
- {
- return $this->urlFactory->create();
+ $this->scopeConfig = $scopeConfig ?:
+ \Magento\Framework\App\ObjectManager::getInstance()->get(ScopeConfigInterface::class);
}
/**
@@ -157,10 +157,19 @@ public function getUrl(\Magento\Catalog\Model\Product $product, $params = [])
UrlRewrite::ENTITY_TYPE => \Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator::ENTITY_TYPE,
UrlRewrite::STORE_ID => $storeId,
];
+ $useCategories = $this->scopeConfig->getValue(
+ \Magento\Catalog\Helper\Product::XML_PATH_PRODUCT_URL_USE_CATEGORY,
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ );
+
if ($categoryId) {
$filterData[UrlRewrite::METADATA]['category_id'] = $categoryId;
+ } elseif (!$useCategories) {
+ $filterData[UrlRewrite::METADATA]['category_id'] = '';
}
+
$rewrite = $this->urlFinder->findOneByData($filterData);
+
if ($rewrite) {
$requestPath = $rewrite->getRequestPath();
$product->setRequestPath($requestPath);
@@ -194,6 +203,7 @@ public function getUrl(\Magento\Catalog\Model\Product $product, $params = [])
$routeParams['_query'] = [];
}
- return $this->getUrlInstance()->setScope($storeId)->getUrl($routePath, $routeParams);
+ $url = $this->urlFactory->create()->setScope($storeId);
+ return $url->getUrl($routePath, $routeParams);
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Website/ReadHandler.php b/app/code/Magento/Catalog/Model/Product/Website/ReadHandler.php
index e81cdedd6d37..8acb4a6593a4 100644
--- a/app/code/Magento/Catalog/Model/Product/Website/ReadHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Website/ReadHandler.php
@@ -9,6 +9,9 @@
use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink;
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
+/**
+ * Add websites ids to product extension attributes.
+ */
class ReadHandler implements ExtensionInterface
{
/**
@@ -18,7 +21,7 @@ class ReadHandler implements ExtensionInterface
/**
* ReadHandler constructor.
- * @param ProductWebsiteLink $resourceModel
+ * @param ProductWebsiteLink $productWebsiteLink
*/
public function __construct(
ProductWebsiteLink $productWebsiteLink
@@ -27,6 +30,8 @@ public function __construct(
}
/**
+ * Add website ids to product extension attributes, if no set.
+ *
* @param ProductInterface $product
* @param array $arguments
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
diff --git a/app/code/Magento/Catalog/Model/ProductCategoryList.php b/app/code/Magento/Catalog/Model/ProductCategoryList.php
index 5bbae772d5c2..c3a88a505c51 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/ProductIdLocator.php b/app/code/Magento/Catalog/Model/ProductIdLocator.php
index 2d9af6829ad6..2d382164f264 100644
--- a/app/code/Magento/Catalog/Model/ProductIdLocator.php
+++ b/app/code/Magento/Catalog/Model/ProductIdLocator.php
@@ -37,23 +37,43 @@ class ProductIdLocator implements \Magento\Catalog\Model\ProductIdLocatorInterfa
*/
private $idsBySku = [];
+ /**
+ * Batch size to iterate collection
+ *
+ * @var int
+ */
+ private $batchSize;
+
/**
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
* @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory
- * @param string $limitIdsBySkuValues
+ * @param string $idsLimit
+ * @param int $batchSize defines how many items can be processed by one iteration
*/
public function __construct(
\Magento\Framework\EntityManager\MetadataPool $metadataPool,
\Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory,
- $idsLimit
+ $idsLimit,
+ int $batchSize = 5000
) {
$this->metadataPool = $metadataPool;
$this->collectionFactory = $collectionFactory;
$this->idsLimit = (int)$idsLimit;
+ $this->batchSize = $batchSize;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
+ * Load product items by provided products SKUs.
+ * Products collection will be iterated by pages with the $this->batchSize as a page size (for a cases when to many
+ * products SKUs were provided in parameters.
+ * Loaded products will be chached in the $this->idsBySku variable, but in the end of the method these storage will
+ * be truncated to $idsLimit quantity.
+ * As a result array with the products data will be returned with the following scheme:
+ * $data['product_sku']['link_field_value' => 'product_type']
+ *
+ * @throws \Exception
*/
public function retrieveProductIdsBySkus(array $skus)
{
@@ -72,8 +92,16 @@ public function retrieveProductIdsBySkus(array $skus)
$linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
->getLinkField();
- foreach ($collection as $item) {
- $this->idsBySku[strtolower(trim($item->getSku()))][$item->getData($linkField)] = $item->getTypeId();
+ $collection->setPageSize($this->batchSize);
+ $pages = $collection->getLastPageNumber();
+ for ($currentPage = 1; $currentPage <= $pages; $currentPage++) {
+ $collection->setCurPage($currentPage);
+ foreach ($collection->getItems() as $item) {
+ $sku = strtolower(trim($item->getSku()));
+ $itemIdentifier = $item->getData($linkField);
+ $this->idsBySku[$sku][$itemIdentifier] = $item->getTypeId();
+ }
+ $collection->clear();
}
}
diff --git a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
index bc212adae2c3..b96aff148e75 100644
--- a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
+++ b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
@@ -9,6 +9,9 @@
use Magento\Catalog\Model\ProductLink\Converter\ConverterPool;
use Magento\Framework\Exception\NoSuchEntityException;
+/**
+ * Provides a collection of linked product items (crosssells, related, upsells, ...)
+ */
class CollectionProvider
{
/**
@@ -47,22 +50,20 @@ public function getCollection(\Magento\Catalog\Model\Product $product, $type)
$products = $this->providers[$type]->getLinkedProducts($product);
$converter = $this->converterPool->getConverter($type);
- $output = [];
$sorterItems = [];
foreach ($products as $item) {
- $output[$item->getId()] = $converter->convert($item);
+ $itemId = $item->getId();
+ $sorterItems[$itemId] = $converter->convert($item);
+ $sorterItems[$itemId]['position'] = $sorterItems[$itemId]['position'] ?? 0;
}
- foreach ($output as $item) {
- $itemPosition = $item['position'];
- if (!isset($sorterItems[$itemPosition])) {
- $sorterItems[$itemPosition] = $item;
- } else {
- $newPosition = $itemPosition + 1;
- $sorterItems[$newPosition] = $item;
- }
- }
- ksort($sorterItems);
+ usort($sorterItems, function ($itemA, $itemB) {
+ $posA = (int)$itemA['position'];
+ $posB = (int)$itemB['position'];
+
+ return $posA <=> $posB;
+ });
+
return $sorterItems;
}
}
diff --git a/app/code/Magento/Catalog/Model/ProductLink/Repository.php b/app/code/Magento/Catalog/Model/ProductLink/Repository.php
index 5bac99dbebbb..98977de7effa 100644
--- a/app/code/Magento/Catalog/Model/ProductLink/Repository.php
+++ b/app/code/Magento/Catalog/Model/ProductLink/Repository.php
@@ -10,6 +10,7 @@
use Magento\Catalog\Api\Data\ProductLinkExtensionFactory;
use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks as LinksInitializer;
use Magento\Catalog\Model\Product\LinkTypeProvider;
+use Magento\Framework\Api\SimpleDataObjectConverter;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\EntityManager\MetadataPool;
@@ -170,7 +171,7 @@ public function getList(\Magento\Catalog\Api\Data\ProductInterface $product)
foreach ($item['custom_attributes'] as $option) {
$name = $option['attribute_code'];
$value = $option['value'];
- $setterName = 'set'.ucfirst($name);
+ $setterName = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($name);
// Check if setter exists
if (method_exists($productLinkExtension, $setterName)) {
call_user_func([$productLinkExtension, $setterName], $value);
diff --git a/app/code/Magento/Catalog/Model/ProductRender.php b/app/code/Magento/Catalog/Model/ProductRender.php
index 702c04b910d4..5efb0343cd99 100644
--- a/app/code/Magento/Catalog/Model/ProductRender.php
+++ b/app/code/Magento/Catalog/Model/ProductRender.php
@@ -206,7 +206,7 @@ public function getExtensionAttributes()
* Set an extension attributes object.
*
* @param \Magento\Catalog\Api\Data\ProductRenderExtensionInterface $extensionAttributes
- * @return $this
+ * @return void
*/
public function setExtensionAttributes(
\Magento\Catalog\Api\Data\ProductRenderExtensionInterface $extensionAttributes
diff --git a/app/code/Magento/Catalog/Model/ProductRender/Image.php b/app/code/Magento/Catalog/Model/ProductRender/Image.php
index 774199a0dbf0..5e024938d37e 100644
--- a/app/code/Magento/Catalog/Model/ProductRender/Image.php
+++ b/app/code/Magento/Catalog/Model/ProductRender/Image.php
@@ -9,14 +9,16 @@
use Magento\Catalog\Api\Data\ProductRender\ImageInterface;
/**
- * @inheritdoc
+ * Product image renderer model.
*/
class Image extends \Magento\Framework\Model\AbstractExtensibleModel implements
ImageInterface
{
/**
+ * Set url to image.
+ *
* @param string $url
- * @return @return void
+ * @return void
*/
public function setUrl($url)
{
@@ -34,6 +36,8 @@ public function getUrl()
}
/**
+ * Retrieve image code.
+ *
* @return string
*/
public function getCode()
@@ -42,6 +46,8 @@ public function getCode()
}
/**
+ * Set image code.
+ *
* @param string $code
* @return void
*/
@@ -51,6 +57,8 @@ public function setCode($code)
}
/**
+ * Set image height.
+ *
* @param string $height
* @return void
*/
@@ -60,6 +68,8 @@ public function setHeight($height)
}
/**
+ * Retrieve image height.
+ *
* @return float
*/
public function getHeight()
@@ -68,6 +78,8 @@ public function getHeight()
}
/**
+ * Retrieve image width.
+ *
* @return float
*/
public function getWidth()
@@ -76,6 +88,8 @@ public function getWidth()
}
/**
+ * Set image width.
+ *
* @param string $width
* @return void
*/
@@ -85,6 +99,8 @@ public function setWidth($width)
}
/**
+ * Retrieve image label.
+ *
* @return string
*/
public function getLabel()
@@ -93,6 +109,8 @@ public function getLabel()
}
/**
+ * Set image label.
+ *
* @param string $label
* @return void
*/
@@ -102,6 +120,8 @@ public function setLabel($label)
}
/**
+ * Retrieve image width after image resize.
+ *
* @return float
*/
public function getResizedWidth()
@@ -110,6 +130,8 @@ public function getResizedWidth()
}
/**
+ * Set image width after image resize.
+ *
* @param string $width
* @return void
*/
@@ -119,6 +141,8 @@ public function setResizedWidth($width)
}
/**
+ * Set image height after image resize.
+ *
* @param string $height
* @return void
*/
@@ -128,6 +152,8 @@ public function setResizedHeight($height)
}
/**
+ * Retrieve image height after image resize.
+ *
* @return float
*/
public function getResizedHeight()
@@ -149,7 +175,7 @@ public function getExtensionAttributes()
* Set an extension attributes object.
*
* @param \Magento\Catalog\Api\Data\ProductRender\ImageExtensionInterface $extensionAttributes
- * @return $this
+ * @return void
*/
public function setExtensionAttributes(
\Magento\Catalog\Api\Data\ProductRender\ImageExtensionInterface $extensionAttributes
diff --git a/app/code/Magento/Catalog/Model/ProductRenderList.php b/app/code/Magento/Catalog/Model/ProductRenderList.php
index a3d906cf10c1..d1f60c098630 100644
--- a/app/code/Magento/Catalog/Model/ProductRenderList.php
+++ b/app/code/Magento/Catalog/Model/ProductRenderList.php
@@ -17,8 +17,8 @@
/**
* Provide product render information (this information should be enough for rendering product on front)
- * for one or few products
*
+ * Render information provided for one or few products
*/
class ProductRenderList implements ProductRenderListInterface
{
@@ -64,7 +64,6 @@ class ProductRenderList implements ProductRenderListInterface
* @param ProductRenderSearchResultsFactory $searchResultFactory
* @param ProductRenderFactory $productRenderDtoFactory
* @param Config $config
- * @param Product\Visibility $productVisibility
* @param CollectionModifier $collectionModifier
* @param array $productAttributes
*/
diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php
index 0dbfda210347..c87b6e976320 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository.php
@@ -1,18 +1,18 @@
productFactory = $productFactory;
$this->collectionFactory = $collectionFactory;
@@ -228,10 +255,14 @@ public function __construct(
$this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\Serialize\Serializer\Json::class);
$this->cacheLimit = (int)$cacheLimit;
+ $this->readExtensions = $readExtensions ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(ReadExtensions::class);
+ $this->linkManagement = $linkManagement ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(CategoryLinkManagementInterface::class);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($sku, $editMode = false, $storeId = null, $forceReload = false)
{
@@ -261,7 +292,7 @@ public function get($sku, $editMode = false, $storeId = null, $forceReload = fal
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getById($productId, $editMode = false, $storeId = null, $forceReload = false)
{
@@ -351,6 +382,8 @@ protected function initializeProductData(array $productData, $createNew)
}
/**
+ * Assign product to websites.
+ *
* @param \Magento\Catalog\Model\Product $product
* @return void
*/
@@ -366,6 +399,11 @@ private function assignProductToWebsites(\Magento\Catalog\Model\Product $product
}
/**
+ * Process new gallery media entry.
+ *
+ * @deprecated
+ * @see MediaGalleryProcessor::processNewMediaGalleryEntry()
+ *
* @param ProductInterface $product
* @param array $newEntry
* @return $this
@@ -377,40 +415,8 @@ protected function processNewMediaGalleryEntry(
ProductInterface $product,
array $newEntry
) {
- /** @var ImageContentInterface $contentDataObject */
- $contentDataObject = $newEntry['content'];
-
- /** @var \Magento\Catalog\Model\Product\Media\Config $mediaConfig */
- $mediaConfig = $product->getMediaConfig();
- $mediaTmpPath = $mediaConfig->getBaseTmpMediaPath();
-
- $relativeFilePath = $this->imageProcessor->processImageContent($mediaTmpPath, $contentDataObject);
- $tmpFilePath = $mediaConfig->getTmpMediaShortUrl($relativeFilePath);
+ $this->getMediaGalleryProcessor()->processNewMediaGalleryEntry($product, $newEntry);
- if (!$product->hasGalleryAttribute()) {
- throw new StateException(
- __("The product that was requested doesn't exist. Verify the product and try again.")
- );
- }
-
- $imageFileUri = $this->getMediaGalleryProcessor()->addImage(
- $product,
- $tmpFilePath,
- isset($newEntry['types']) ? $newEntry['types'] : [],
- true,
- isset($newEntry['disabled']) ? $newEntry['disabled'] : true
- );
- // Update additional fields that are still empty after addImage call
- $this->getMediaGalleryProcessor()->updateImage(
- $product,
- $imageFileUri,
- [
- 'label' => $newEntry['label'],
- 'position' => $newEntry['position'],
- 'disabled' => $newEntry['disabled'],
- 'media_type' => $newEntry['media_type'],
- ]
- );
return $this;
}
@@ -485,68 +491,13 @@ private function processLinks(ProductInterface $product, $newLinks)
* @return $this
* @throws InputException
* @throws StateException
+ * @throws LocalizedException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
protected function processMediaGallery(ProductInterface $product, $mediaGalleryEntries)
{
- $existingMediaGallery = $product->getMediaGallery('images');
- $newEntries = [];
- $entriesById = [];
- if (!empty($existingMediaGallery)) {
- foreach ($mediaGalleryEntries as $entry) {
- if (isset($entry['value_id'])) {
- $entriesById[$entry['value_id']] = $entry;
- } else {
- $newEntries[] = $entry;
- }
- }
- foreach ($existingMediaGallery as $key => &$existingEntry) {
- if (isset($entriesById[$existingEntry['value_id']])) {
- $updatedEntry = $entriesById[$existingEntry['value_id']];
- if ($updatedEntry['file'] === null) {
- unset($updatedEntry['file']);
- }
- $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry);
- } else {
- //set the removed flag
- $existingEntry['removed'] = true;
- }
- }
- $product->setData('media_gallery', ["images" => $existingMediaGallery]);
- } else {
- $newEntries = $mediaGalleryEntries;
- }
-
- $images = (array)$product->getMediaGallery('images');
- $images = $this->determineImageRoles($product, $images);
+ $this->getMediaGalleryProcessor()->processMediaGallery($product, $mediaGalleryEntries);
- $this->getMediaGalleryProcessor()->clearMediaAttribute($product, array_keys($product->getMediaAttributes()));
-
- foreach ($images as $image) {
- if (!isset($image['removed']) && !empty($image['types'])) {
- $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']);
- }
- }
-
- foreach ($newEntries as $newEntry) {
- if (!isset($newEntry['content'])) {
- throw new InputException(__('The image content is invalid. Verify the content and try again.'));
- }
- /** @var ImageContentInterface $contentDataObject */
- $contentDataObject = $this->contentFactory->create()
- ->setName($newEntry['content']['data'][ImageContentInterface::NAME])
- ->setBase64EncodedData($newEntry['content']['data'][ImageContentInterface::BASE64_ENCODED_DATA])
- ->setType($newEntry['content']['data'][ImageContentInterface::TYPE]);
- $newEntry['content'] = $contentDataObject;
- $this->processNewMediaGalleryEntry($product, $newEntry);
-
- $finalGallery = $product->getData('media_gallery');
- $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById));
- $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]);
- $entriesById[$newEntryId] = $newEntry;
- $finalGallery['images'][$newEntryId] = $newEntry;
- $product->setData('media_gallery', $finalGallery);
- }
return $this;
}
@@ -557,6 +508,7 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE
*/
public function save(ProductInterface $product, $saveOptions = false)
{
+ $assignToCategories = false;
$tierPrices = $product->getData('tier_price');
try {
@@ -574,6 +526,7 @@ public function save(ProductInterface $product, $saveOptions = false)
$extensionAttributes = $product->getExtensionAttributes();
if (empty($extensionAttributes->__toArray())) {
$product->setExtensionAttributes($existingProduct->getExtensionAttributes());
+ $assignToCategories = true;
}
} catch (NoSuchEntityException $e) {
$existingProduct = null;
@@ -611,6 +564,12 @@ public function save(ProductInterface $product, $saveOptions = false)
}
$this->saveProduct($product);
+ if ($assignToCategories === true && $product->getCategoryIds()) {
+ $this->linkManagement->assignProductToCategories(
+ $product->getSku(),
+ $product->getCategoryIds()
+ );
+ }
$this->removeProductFromLocalCache($product->getSku());
unset($this->instancesById[$product->getId()]);
@@ -618,7 +577,7 @@ public function save(ProductInterface $product, $saveOptions = false)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(ProductInterface $product)
{
@@ -629,10 +588,11 @@ public function delete(ProductInterface $product)
unset($this->instancesById[$product->getId()]);
$this->resourceModel->delete($product);
} catch (ValidatorException $e) {
- throw new CouldNotSaveException(__($e->getMessage()));
+ throw new CouldNotSaveException(__($e->getMessage()), $e);
} catch (\Exception $e) {
throw new \Magento\Framework\Exception\StateException(
- __('The "%1" product couldn\'t be removed.', $sku)
+ __('The "%1" product couldn\'t be removed.', $sku),
+ $e
);
}
$this->removeProductFromLocalCache($sku);
@@ -642,7 +602,7 @@ public function delete(ProductInterface $product)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteById($sku)
{
@@ -651,7 +611,7 @@ public function deleteById($sku)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
{
@@ -668,6 +628,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr
$collection->load();
$collection->addCategoryIds();
+ $this->addExtensionAttributes($collection);
$searchResult = $this->searchResultsFactory->create();
$searchResult->setSearchCriteria($searchCriteria);
$searchResult->setItems($collection->getItems());
@@ -678,7 +639,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr
$this->getCacheKey(
[
false,
- $product->hasData(\Magento\Catalog\Model\Product::STORE_ID) ? $product->getStoreId() : null
+ $product->getStoreId()
]
),
$product
@@ -688,6 +649,20 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr
return $searchResult;
}
+ /**
+ * Add extension attributes to loaded items.
+ *
+ * @param Collection $collection
+ * @return Collection
+ */
+ private function addExtensionAttributes(Collection $collection) : Collection
+ {
+ foreach ($collection->getItems() as $item) {
+ $this->readExtensions->execute($item);
+ }
+ return $collection;
+ }
+
/**
* Helper function that adds a FilterGroup to the collection.
*
@@ -733,41 +708,18 @@ public function cleanCache()
}
/**
- * Ascertain image roles, if they are not set against the gallery entries
+ * Retrieve media gallery processor.
*
- * @param ProductInterface $product
- * @param array $images
- * @return array
- */
- private function determineImageRoles(ProductInterface $product, array $images) : array
- {
- $imagesWithRoles = [];
- foreach ($images as $image) {
- if (!isset($image['types'])) {
- $image['types'] = [];
- if (isset($image['file'])) {
- foreach (array_keys($product->getMediaAttributes()) as $attribute) {
- if ($image['file'] == $product->getData($attribute)) {
- $image['types'][] = $attribute;
- }
- }
- }
- }
- $imagesWithRoles[] = $image;
- }
- return $imagesWithRoles;
- }
-
- /**
- * @return Product\Gallery\Processor
+ * @return MediaGalleryProcessor
*/
private function getMediaGalleryProcessor()
{
- if (null === $this->mediaGalleryProcessor) {
- $this->mediaGalleryProcessor = \Magento\Framework\App\ObjectManager::getInstance()
- ->get(\Magento\Catalog\Model\Product\Gallery\Processor::class);
+ if (null === $this->mediaProcessor) {
+ $this->mediaProcessor = \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(MediaGalleryProcessor::class);
}
- return $this->mediaGalleryProcessor;
+
+ return $this->mediaProcessor;
}
/**
@@ -879,6 +831,7 @@ private function saveProduct($product): void
throw new CouldNotSaveException(__($e->getMessage()));
} catch (LocalizedException $e) {
throw $e;
+ // phpcs:disable Magento2.Exceptions.ThrowCatch
} catch (\Exception $e) {
throw new CouldNotSaveException(
__('The product was unable to be saved. Please try again.'),
diff --git a/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php
new file mode 100644
index 000000000000..70311954f63e
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php
@@ -0,0 +1,239 @@
+processor = $processor;
+ $this->contentFactory = $contentFactory;
+ $this->imageProcessor = $imageProcessor;
+ }
+
+ /**
+ * Process Media gallery data before save product.
+ *
+ * Compare Media Gallery Entries Data with existing Media Gallery
+ * * If Media entry has not value_id set it as new
+ * * If Existing entry 'value_id' absent in Media Gallery set 'removed' flag
+ * * Merge Existing and new media gallery
+ *
+ * @param ProductInterface $product contains only existing media gallery items
+ * @param array $mediaGalleryEntries array which contains all media gallery items
+ * @return void
+ * @throws InputException
+ * @throws StateException
+ * @throws LocalizedException
+ */
+ public function processMediaGallery(ProductInterface $product, array $mediaGalleryEntries) :void
+ {
+ $existingMediaGallery = $product->getMediaGallery('images');
+ $newEntries = [];
+ $entriesById = [];
+ if (!empty($existingMediaGallery)) {
+ foreach ($mediaGalleryEntries as $entry) {
+ if (isset($entry['value_id'])) {
+ $entriesById[$entry['value_id']] = $entry;
+ } else {
+ $newEntries[] = $entry;
+ }
+ }
+ foreach ($existingMediaGallery as $key => &$existingEntry) {
+ if (isset($entriesById[$existingEntry['value_id']])) {
+ $updatedEntry = $entriesById[$existingEntry['value_id']];
+ if ($updatedEntry['file'] === null) {
+ unset($updatedEntry['file']);
+ }
+ $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry);
+ } else {
+ //set the removed flag
+ $existingEntry['removed'] = true;
+ }
+ }
+ $product->setData('media_gallery', ["images" => $existingMediaGallery]);
+ } else {
+ $newEntries = $mediaGalleryEntries;
+ }
+
+ $images = (array)$product->getMediaGallery('images');
+ $images = $this->determineImageRoles($product, $images);
+
+ $this->processor->clearMediaAttribute($product, array_keys($product->getMediaAttributes()));
+
+ $this->processMediaAttributes($product, $images);
+ $this->processEntries($product, $newEntries, $entriesById);
+ }
+
+ /**
+ * Process new gallery media entry.
+ *
+ * @param ProductInterface $product
+ * @param array $newEntry
+ * @return void
+ * @throws InputException
+ * @throws StateException
+ * @throws LocalizedException
+ */
+ public function processNewMediaGalleryEntry(
+ ProductInterface $product,
+ array $newEntry
+ ) :void {
+ /** @var ImageContentInterface $contentDataObject */
+ $contentDataObject = $newEntry['content'];
+
+ /** @var Config $mediaConfig */
+ $mediaConfig = $product->getMediaConfig();
+ $mediaTmpPath = $mediaConfig->getBaseTmpMediaPath();
+
+ $relativeFilePath = $this->imageProcessor->processImageContent($mediaTmpPath, $contentDataObject);
+ $tmpFilePath = $mediaConfig->getTmpMediaShortUrl($relativeFilePath);
+
+ if (!$product->hasGalleryAttribute()) {
+ throw new StateException(
+ __("The product that was requested doesn't exist. Verify the product and try again.")
+ );
+ }
+
+ $imageFileUri = $this->processor->addImage(
+ $product,
+ $tmpFilePath,
+ isset($newEntry['types']) ? $newEntry['types'] : [],
+ true,
+ isset($newEntry['disabled']) ? $newEntry['disabled'] : true
+ );
+ // Update additional fields that are still empty after addImage call
+ $this->processor->updateImage(
+ $product,
+ $imageFileUri,
+ [
+ 'label' => $newEntry['label'],
+ 'position' => $newEntry['position'],
+ 'disabled' => $newEntry['disabled'],
+ 'media_type' => $newEntry['media_type'],
+ ]
+ );
+ }
+
+ /**
+ * Ascertain image roles, if they are not set against the gallery entries.
+ *
+ * @param ProductInterface $product
+ * @param array $images
+ * @return array
+ */
+ private function determineImageRoles(ProductInterface $product, array $images) : array
+ {
+ $imagesWithRoles = [];
+ foreach ($images as $image) {
+ if (!isset($image['types'])) {
+ $image['types'] = [];
+ if (isset($image['file'])) {
+ foreach (array_keys($product->getMediaAttributes()) as $attribute) {
+ if ($image['file'] == $product->getData($attribute)) {
+ $image['types'][] = $attribute;
+ }
+ }
+ }
+ }
+ $imagesWithRoles[] = $image;
+ }
+
+ return $imagesWithRoles;
+ }
+
+ /**
+ * Convert entries into product media gallery data and set to product.
+ *
+ * @param ProductInterface $product
+ * @param array $newEntries
+ * @param array $entriesById
+ * @throws InputException
+ * @throws LocalizedException
+ * @throws StateException
+ */
+ private function processEntries(ProductInterface $product, array $newEntries, array $entriesById): void
+ {
+ foreach ($newEntries as $newEntry) {
+ if (!isset($newEntry['content'])) {
+ throw new InputException(__('The image content is invalid. Verify the content and try again.'));
+ }
+ /** @var ImageContentInterface $contentDataObject */
+ $contentDataObject = $this->contentFactory->create()
+ ->setName($newEntry['content']['data'][ImageContentInterface::NAME])
+ ->setBase64EncodedData($newEntry['content']['data'][ImageContentInterface::BASE64_ENCODED_DATA])
+ ->setType($newEntry['content']['data'][ImageContentInterface::TYPE]);
+ $newEntry['content'] = $contentDataObject;
+ $this->processNewMediaGalleryEntry($product, $newEntry);
+
+ $finalGallery = $product->getData('media_gallery');
+ $newEntryId = key(array_diff_key($product->getData('media_gallery')['images'], $entriesById));
+ $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]);
+ $entriesById[$newEntryId] = $newEntry;
+ $finalGallery['images'][$newEntryId] = $newEntry;
+ $product->setData('media_gallery', $finalGallery);
+ }
+ }
+
+ /**
+ * Set media attribute values.
+ *
+ * @param ProductInterface $product
+ * @param array $images
+ */
+ private function processMediaAttributes(ProductInterface $product, array $images): void
+ {
+ foreach ($images as $image) {
+ if (!isset($image['removed']) && !empty($image['types'])) {
+ $this->processor->setMediaAttribute($product, $image['types'], $image['file']);
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php
index d4f5fdd5137c..2896849b76cc 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractCollection.php
@@ -7,6 +7,7 @@
/**
* Flat abstract collection
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
abstract class AbstractCollection extends \Magento\Framework\Model\ResourceModel\Db\VersionControl\Collection
@@ -34,7 +35,7 @@ public function setSelectCountSql(\Magento\Framework\DB\Select $countSelect)
}
/**
- * get select count sql
+ * Get select count sql
*
* @return \Magento\Framework\DB\Select
*/
@@ -69,6 +70,7 @@ protected function _attributeToField($attribute)
/**
* Add attribute to select result set.
+ *
* Backward compatibility with EAV collection
*
* @param string $attribute
@@ -82,6 +84,7 @@ public function addAttributeToSelect($attribute)
/**
* Specify collection select filter by attribute value
+ *
* Backward compatibility with EAV collection
*
* @param string|\Magento\Eav\Model\Entity\Attribute $attribute
@@ -96,6 +99,7 @@ public function addAttributeToFilter($attribute, $condition = null)
/**
* Specify collection select order by attribute value
+ *
* Backward compatibility with EAV collection
*
* @param string $attribute
@@ -110,6 +114,7 @@ public function addAttributeToSort($attribute, $dir = 'asc')
/**
* Set collection page start and records to show
+ *
* Backward compatibility with EAV collection
*
* @param int $pageNum
@@ -124,11 +129,12 @@ public function setPage($pageNum, $pageSize)
/**
* Create all ids retrieving select with limitation
+ *
* Backward compatibility with EAV collection
*
* @param int $limit
* @param int $offset
- * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection
+ * @return \Magento\Framework\DB\Select
*/
protected function _getAllIdsSelect($limit = null, $offset = null)
{
@@ -144,6 +150,7 @@ protected function _getAllIdsSelect($limit = null, $offset = null)
/**
* Retrieve all ids for collection
+ *
* Backward compatibility with EAV collection
*
* @param int $limit
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
index b9e629912a5b..3d7f863b7c0d 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
@@ -7,6 +7,10 @@
namespace Magento\Catalog\Model\ResourceModel;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
+use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
+use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
+use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
/**
* Catalog entity abstract model
@@ -37,16 +41,18 @@ abstract class AbstractResource extends \Magento\Eav\Model\Entity\AbstractEntity
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Model\Factory $modelFactory
* @param array $data
+ * @param UniqueValidationInterface|null $uniqueValidator
*/
public function __construct(
\Magento\Eav\Model\Entity\Context $context,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Model\Factory $modelFactory,
- $data = []
+ $data = [],
+ UniqueValidationInterface $uniqueValidator = null
) {
$this->_storeManager = $storeManager;
$this->_modelFactory = $modelFactory;
- parent::__construct($context, $data);
+ parent::__construct($context, $data, $uniqueValidator);
}
/**
@@ -86,16 +92,14 @@ protected function _isApplicableAttribute($object, $attribute)
/**
* Check whether attribute instance (attribute, backend, frontend or source) has method and applicable
*
- * @param AbstractAttribute|\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
- * |\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend
- * |\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource $instance
+ * @param AbstractAttribute|AbstractBackend|AbstractFrontend|AbstractSource $instance
* @param string $method
* @param array $args array of arguments
* @return boolean
*/
protected function _isCallableAttributeInstance($instance, $method, $args)
{
- if ($instance instanceof \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
+ if ($instance instanceof AbstractBackend
&& ($method == 'beforeSave' || $method == 'afterSave')
) {
$attributeCode = $instance->getAttribute()->getAttributeCode();
@@ -112,6 +116,7 @@ protected function _isCallableAttributeInstance($instance, $method, $args)
/**
* Retrieve select object for loading entity attributes values
+ *
* Join attribute store value
*
* @param \Magento\Framework\DataObject $object
@@ -244,6 +249,7 @@ protected function _saveAttributeValue($object, $attribute, $value)
/**
* Check if attribute present for non default Store View.
+ *
* Prevent "delete" query locking in a case when nothing to delete
*
* @param AbstractAttribute $attribute
@@ -485,7 +491,7 @@ protected function _canUpdateAttribute(AbstractAttribute $attribute, $value, arr
* Retrieve attribute's raw value from DB.
*
* @param int $entityId
- * @param int|string|array $attribute atrribute's ids or codes
+ * @param int|string|array $attribute attribute's ids or codes
* @param int|\Magento\Store\Model\Store $store
* @return bool|string|array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
index 9de0e8a84904..536fda7e093d 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
@@ -16,6 +16,8 @@
use Magento\Framework\EntityManager\EntityManager;
/**
+ * Resource model for category entity
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Category extends AbstractResource
@@ -198,7 +200,7 @@ protected function _getTree()
* delete child categories
*
* @param \Magento\Framework\DataObject $object
- * @return $this
+ * @return void
*/
protected function _beforeDelete(\Magento\Framework\DataObject $object)
{
@@ -249,7 +251,8 @@ public function deleteChildren(\Magento\Framework\DataObject $object)
/**
* Process category data before saving
- * prepare path and increment children count for parent categories
+ *
+ * Prepare path and increment children count for parent categories
*
* @param \Magento\Framework\DataObject $object
* @return $this
@@ -298,7 +301,8 @@ protected function _beforeSave(\Magento\Framework\DataObject $object)
/**
* Process category data after save category object
- * save related products ids and update path value
+ *
+ * Save related products ids and update path value
*
* @param \Magento\Framework\DataObject $object
* @return $this
@@ -482,8 +486,20 @@ public function getProductsPosition($category)
$this->getCategoryProductTable(),
['product_id', 'position']
)->where(
- 'category_id = :category_id'
+ "{$this->getTable('catalog_category_product')}.category_id = ?",
+ $category->getId()
);
+ $websiteId = $category->getStore()->getWebsiteId();
+ if ($websiteId) {
+ $select->join(
+ ['product_website' => $this->getTable('catalog_product_website')],
+ "product_website.product_id = {$this->getTable('catalog_category_product')}.product_id",
+ []
+ )->where(
+ 'product_website.website_id = ?',
+ $websiteId
+ );
+ }
$bind = ['category_id' => (int)$category->getId()];
return $this->getConnection()->fetchPairs($select, $bind);
@@ -664,7 +680,7 @@ public function getProductCount($category)
$bind = ['category_id' => (int)$category->getId()];
$counts = $this->getConnection()->fetchOne($select, $bind);
- return intval($counts);
+ return (int) $counts;
}
/**
@@ -862,6 +878,7 @@ public function isInRootCategoryList($category)
/**
* Check category is forbidden to delete.
+ *
* If category is root and assigned to store group return false
*
* @param integer $categoryId
@@ -918,7 +935,7 @@ public function changeParent(
$childrenCount = $this->getChildrenCount($category->getId()) + 1;
$table = $this->getEntityTable();
$connection = $this->getConnection();
- $levelFiled = $connection->quoteIdentifier('level');
+ $levelField = $connection->quoteIdentifier('level');
$pathField = $connection->quoteIdentifier('path');
/**
@@ -958,7 +975,7 @@ public function changeParent(
$newPath . '/'
) . ')'
),
- 'level' => new \Zend_Db_Expr($levelFiled . ' + ' . $levelDisposition)
+ 'level' => new \Zend_Db_Expr($levelField . ' + ' . $levelDisposition)
],
[$pathField . ' LIKE ?' => $category->getPath() . '/%']
);
@@ -982,6 +999,7 @@ public function changeParent(
/**
* Process positions of old parent category children and new parent category children.
+ *
* Get position for moved category
*
* @param \Magento\Catalog\Model\Category $category
@@ -1062,7 +1080,7 @@ public function load($object, $entityId, $attributes = [])
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete($object)
{
@@ -1088,6 +1106,8 @@ public function save(\Magento\Framework\Model\AbstractModel $object)
}
/**
+ * Returns EntityManager object
+ *
* @return EntityManager
*/
private function getEntityManager()
@@ -1100,6 +1120,8 @@ private function getEntityManager()
}
/**
+ * Returns AggregateCount object
+ *
* @return Category\AggregateCount
*/
private function getAggregateCount()
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
index 46bb74513b59..657daca13055 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
@@ -82,7 +82,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
- *
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -323,9 +322,7 @@ public function loadProductCount($items, $countRegular = true, $countAnchor = tr
'main_table.category_id=e.entity_id',
[]
)->where(
- 'e.entity_id = :entity_id'
- )->orWhere(
- 'e.path LIKE :c_path'
+ '(e.entity_id = :entity_id OR e.path LIKE :c_path)'
);
if ($websiteId) {
$select->join(
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php
index 01e4b072b036..05950531e217 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php
@@ -173,7 +173,7 @@ public function getMainTable()
public function getMainStoreTable($storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID)
{
if (is_string($storeId)) {
- $storeId = intval($storeId);
+ $storeId = (int) $storeId;
}
if ($storeId) {
@@ -699,8 +699,20 @@ public function getProductsPosition($category)
$this->getTable('catalog_category_product'),
['product_id', 'position']
)->where(
- 'category_id = :category_id'
+ "{$this->getTable('catalog_category_product')}.category_id = ?",
+ $category->getId()
);
+ $websiteId = $category->getStore()->getWebsiteId();
+ if ($websiteId) {
+ $select->join(
+ ['product_website' => $this->getTable('catalog_product_website')],
+ "product_website.product_id = {$this->getTable('catalog_category_product')}.product_id",
+ []
+ )->where(
+ 'product_website.website_id = ?',
+ $websiteId
+ );
+ }
$bind = ['category_id' => (int)$category->getId()];
return $this->getConnection()->fetchPairs($select, $bind);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php b/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php
index 9ab863cde270..3a0d47fe573f 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php
@@ -7,6 +7,7 @@
/**
* Catalog EAV collection resource abstract model
+ *
* Implement using different stores for retrieve attribute values
*
* @api
@@ -42,7 +43,6 @@ class AbstractCollection extends \Magento\Eav\Model\Entity\Collection\AbstractCo
* @param \Magento\Framework\Validator\UniversalFactory $universalFactory
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
- *
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -205,10 +205,7 @@ protected function _getLoadAttributesSelect($table, $attributeIds = [])
}
/**
- * @param \Magento\Framework\DB\Select $select
- * @param string $table
- * @param string $type
- * @return \Magento\Framework\DB\Select
+ * @inheritdoc
*/
protected function _addLoadAttributesSelectValues($select, $table, $type)
{
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
index 8f8e9f6bfedf..d56cc40ad0fc 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
@@ -167,6 +167,8 @@ public function __construct(
}
/**
+ * Init model
+ *
* @return void
*/
protected function _construct()
@@ -234,6 +236,8 @@ public function afterSave()
) {
$this->_indexerEavProcessor->markIndexerAsInvalid();
}
+
+ $this->_source = null;
return parent::afterSave();
}
@@ -362,6 +366,7 @@ public function getStoreId()
/**
* Retrieve apply to products array
+ *
* Return empty array if applied to all products
*
* @return string[]
@@ -478,7 +483,7 @@ protected function _isOriginalIndexable()
$backendType = $this->getOrigData('backend_type');
$frontendInput = $this->getOrigData('frontend_input');
- if ($backendType == 'int' && $frontendInput == 'select') {
+ if ($backendType == 'int' && ($frontendInput == 'select' || $frontendInput == 'boolean')) {
return true;
} elseif ($backendType == 'varchar' && $frontendInput == 'multiselect') {
return true;
@@ -507,8 +512,8 @@ public function getIndexType()
}
/**
+ * @inheritdoc
* @codeCoverageIgnoreStart
- * {@inheritdoc}
*/
public function getIsWysiwygEnabled()
{
@@ -516,7 +521,7 @@ public function getIsWysiwygEnabled()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsHtmlAllowedOnFront()
{
@@ -524,7 +529,7 @@ public function getIsHtmlAllowedOnFront()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getUsedForSortBy()
{
@@ -532,7 +537,7 @@ public function getUsedForSortBy()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsFilterable()
{
@@ -540,7 +545,7 @@ public function getIsFilterable()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsFilterableInSearch()
{
@@ -548,7 +553,7 @@ public function getIsFilterableInSearch()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsUsedInGrid()
{
@@ -556,7 +561,7 @@ public function getIsUsedInGrid()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsVisibleInGrid()
{
@@ -564,7 +569,7 @@ public function getIsVisibleInGrid()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsFilterableInGrid()
{
@@ -572,7 +577,7 @@ public function getIsFilterableInGrid()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getPosition()
{
@@ -580,7 +585,7 @@ public function getPosition()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsSearchable()
{
@@ -588,7 +593,7 @@ public function getIsSearchable()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsVisibleInAdvancedSearch()
{
@@ -596,7 +601,7 @@ public function getIsVisibleInAdvancedSearch()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsComparable()
{
@@ -604,7 +609,7 @@ public function getIsComparable()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsUsedForPromoRules()
{
@@ -612,7 +617,7 @@ public function getIsUsedForPromoRules()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsVisibleOnFront()
{
@@ -620,7 +625,7 @@ public function getIsVisibleOnFront()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getUsedInProductListing()
{
@@ -628,7 +633,7 @@ public function getUsedInProductListing()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getIsVisible()
{
@@ -638,7 +643,7 @@ public function getIsVisible()
//@codeCoverageIgnoreEnd
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getScope()
{
@@ -720,7 +725,7 @@ public function setPosition($position)
/**
* Set apply to value for the element
*
- * @param string []|string
+ * @param string[]|string $applyTo
* @return $this
*/
public function setApplyTo($applyTo)
@@ -829,7 +834,7 @@ public function setScope($scope)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function afterDelete()
{
@@ -840,9 +845,14 @@ public function afterDelete()
/**
* @inheritdoc
* @since 100.0.9
+ *
+ * @SuppressWarnings(PHPMD.SerializationAware)
+ * @deprecated Do not use PHP serialization.
*/
public function __sleep()
{
+ trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
+
$this->unsetData('entity_type');
return array_diff(
parent::__sleep(),
@@ -853,9 +863,14 @@ public function __sleep()
/**
* @inheritdoc
* @since 100.0.9
+ *
+ * @SuppressWarnings(PHPMD.SerializationAware)
+ * @deprecated Do not use PHP serialization.
*/
public function __wakeup()
{
+ trigger_error('Using PHP serialization is deprecated', E_USER_DEPRECATED);
+
parent::__wakeup();
$objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$this->_indexerEavProcessor = $objectManager->get(\Magento\Catalog\Model\Indexer\Product\Flat\Processor::class);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php b/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php
index 3699e29f8201..585da2af529a 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php
@@ -112,7 +112,7 @@ public function getCount($range)
/**
* Check and set correct variable values to prevent SQL-injections
*/
- $range = floatval($range);
+ $range = (float)$range;
if ($range == 0) {
$range = 1;
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
index d71ec2388198..24174391be82 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
@@ -8,6 +8,7 @@
use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
/**
* Product entity resource model
@@ -101,6 +102,7 @@ class Product extends AbstractResource
* @param \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes
* @param array $data
* @param TableMaintainer|null $tableMaintainer
+ * @param UniqueValidationInterface|null $uniqueValidator
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -115,7 +117,8 @@ public function __construct(
\Magento\Eav\Model\Entity\TypeFactory $typeFactory,
\Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes,
$data = [],
- TableMaintainer $tableMaintainer = null
+ TableMaintainer $tableMaintainer = null,
+ UniqueValidationInterface $uniqueValidator = null
) {
$this->_categoryCollectionFactory = $categoryCollectionFactory;
$this->_catalogCategory = $catalogCategory;
@@ -127,7 +130,8 @@ public function __construct(
$context,
$storeManager,
$modelFactory,
- $data
+ $data,
+ $uniqueValidator
);
$this->connectionName = 'catalog';
$this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class);
@@ -289,7 +293,7 @@ protected function _afterSave(\Magento\Framework\DataObject $product)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete($object)
{
@@ -593,7 +597,7 @@ public function countAll()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function validate($object)
{
@@ -633,7 +637,7 @@ public function load($object, $entityId, $attributes = [])
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
* @since 101.0.0
*/
@@ -675,6 +679,8 @@ public function save(\Magento\Framework\Model\AbstractModel $object)
}
/**
+ * Retrieve entity manager object
+ *
* @return \Magento\Framework\EntityManager\EntityManager
*/
private function getEntityManager()
@@ -687,6 +693,8 @@ private function getEntityManager()
}
/**
+ * Retrieve ProductWebsiteLink object
+ *
* @deprecated 101.1.0
* @return ProductWebsiteLink
*/
@@ -696,6 +704,8 @@ private function getProductWebsiteLink()
}
/**
+ * Retrieve CategoryLink object
+ *
* @deprecated 101.1.0
* @return \Magento\Catalog\Model\ResourceModel\Product\CategoryLink
*/
@@ -710,9 +720,10 @@ private function getProductCategoryLink()
/**
* Extends parent method to be appropriate for product.
+ *
* Store id is required to correctly identify attribute value we are working with.
*
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.1.0
*/
protected function getAttributeRow($entity, $object, $attribute)
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php
index d97f6bebf4e9..da3c4fb4417f 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php
@@ -9,6 +9,7 @@
/**
* Interface BaseSelectProcessorInterface
+ *
* @api
* @since 101.0.3
*/
@@ -20,6 +21,8 @@ interface BaseSelectProcessorInterface
const PRODUCT_TABLE_ALIAS = 'child';
/**
+ * Process the select statement
+ *
* @param Select $select
* @return Select
* @since 101.0.3
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php
index b54c19a11150..cf5760b0c33a 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/CategoryLink.php
@@ -93,6 +93,8 @@ public function saveCategoryLinks(ProductInterface $product, array $categoryLink
}
/**
+ * Get category link metadata
+ *
* @return \Magento\Framework\EntityManager\EntityMetadataInterface
*/
private function getCategoryLinkMetadata()
@@ -114,16 +116,22 @@ private function getCategoryLinkMetadata()
private function processCategoryLinks($newCategoryPositions, &$oldCategoryPositions)
{
$result = ['changed' => [], 'updated' => []];
+
+ $oldCategoryPositions = array_values($oldCategoryPositions);
foreach ($newCategoryPositions as $newCategoryPosition) {
- $key = array_search(
- $newCategoryPosition['category_id'],
- array_column($oldCategoryPositions, 'category_id')
- );
+ $key = false;
+
+ foreach ($oldCategoryPositions as $oldKey => $oldCategoryPosition) {
+ if ((int)$oldCategoryPosition['category_id'] === (int)$newCategoryPosition['category_id']) {
+ $key = $oldKey;
+ break;
+ }
+ }
if ($key === false) {
$result['changed'][] = $newCategoryPosition;
} elseif ($oldCategoryPositions[$key]['position'] != $newCategoryPosition['position']) {
- $result['updated'][] = $newCategoryPositions[$key];
+ $result['updated'][] = $newCategoryPosition;
unset($oldCategoryPositions[$key]);
}
}
@@ -132,6 +140,8 @@ private function processCategoryLinks($newCategoryPositions, &$oldCategoryPositi
}
/**
+ * Update category links
+ *
* @param ProductInterface $product
* @param array $insertLinks
* @param bool $insert
@@ -175,6 +185,8 @@ private function updateCategoryLinks(ProductInterface $product, array $insertLin
}
/**
+ * Delete category links
+ *
* @param ProductInterface $product
* @param array $deleteLinks
* @return array
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
index 4384effc4e35..0cdf8b39f7d5 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -7,6 +7,8 @@
namespace Magento\Catalog\Model\ResourceModel\Product;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
use Magento\Catalog\Model\Product\Gallery\ReadHandler as GalleryReadHandler;
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
@@ -16,11 +18,10 @@
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\MetadataPool;
-use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
use Magento\Store\Model\Store;
-use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
-use Magento\Framework\Indexer\DimensionFactory;
/**
* Product collection
@@ -31,6 +32,7 @@
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.NumberOfChildren)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @since 100.0.2
*/
class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection
@@ -290,8 +292,14 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
*/
private $dimensionFactory;
+ /**
+ * @var \Magento\Framework\DataObject
+ */
+ private $emptyItem;
+
/**
* Collection constructor
+ *
* @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
* @param \Psr\Log\LoggerInterface $logger
* @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
@@ -317,6 +325,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
* @param TableMaintainer|null $tableMaintainer
* @param PriceTableResolver|null $priceTableResolver
* @param DimensionFactory|null $dimensionFactory
+ *
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -434,7 +443,7 @@ protected function _preparePriceExpressionParameters($select)
*/
public function getPriceExpression($select)
{
- //@todo: Add caching of price expresion
+ //@todo: Add caching of price expression
$this->_preparePriceExpressionParameters($select);
return $this->_priceExpression;
}
@@ -474,8 +483,7 @@ public function getFlatState()
}
/**
- * Retrieve is flat enabled flag
- * Return always false if magento run admin
+ * Retrieve is flat enabled. Return always false if magento run admin.
*
* @return bool
*/
@@ -506,8 +514,7 @@ protected function _construct()
}
/**
- * Standard resource collection initialization
- * Needed for child classes
+ * Standard resource collection initialization. Needed for child classes.
*
* @param string $model
* @param string $entityModel
@@ -546,14 +553,16 @@ protected function _prepareStaticFields()
}
/**
- * Retrieve collection empty item
- * Redeclared for specifying id field name without getting resource model inside model
+ * Get collection empty item. Redeclared for specifying id field name without getting resource model inside model.
*
* @return \Magento\Framework\DataObject
*/
public function getNewEmptyItem()
{
- $object = parent::getNewEmptyItem();
+ if (null === $this->emptyItem) {
+ $this->emptyItem = parent::getNewEmptyItem();
+ }
+ $object = clone $this->emptyItem;
if ($this->isEnabledFlat()) {
$object->setIdFieldName($this->getEntity()->getIdFieldName());
}
@@ -633,8 +642,7 @@ public function _loadAttributes($printQuery = false, $logQuery = false)
}
/**
- * Add attribute to entities in collection
- * If $attribute=='*' select all attributes
+ * Add attribute to entities in collection. If $attribute=='*' select all attributes.
*
* @param array|string|integer|\Magento\Framework\App\Config\Element $attribute
* @param bool|string $joinType
@@ -670,8 +678,7 @@ public function addAttributeToSelect($attribute, $joinType = false)
}
/**
- * Processing collection items after loading
- * Adding url rewrites, minimal prices, final prices, tax percents
+ * Processing collection items after loading. Adding url rewrites, minimal prices, final prices, tax percents.
*
* @return $this
*/
@@ -682,6 +689,7 @@ protected function _afterLoad()
}
$this->_prepareUrlDataObject();
+ $this->prepareStoreId();
if (count($this)) {
$this->_eventManager->dispatch('catalog_product_collection_load_after', ['collection' => $this]);
@@ -690,6 +698,23 @@ protected function _afterLoad()
return $this;
}
+ /**
+ * Add Store ID to products from collection.
+ *
+ * @return $this
+ */
+ protected function prepareStoreId()
+ {
+ if ($this->getStoreId() !== null) {
+ /** @var $item \Magento\Catalog\Model\Product */
+ foreach ($this->_items as $item) {
+ $item->setStoreId($this->getStoreId());
+ }
+ }
+
+ return $this;
+ }
+
/**
* Prepare Url Data object
*
@@ -756,8 +781,7 @@ public function addIdFilter($productId, $exclude = false)
}
/**
- * Adding product website names to result collection
- * Add for each product websites information
+ * Adding product website names to result collection. Add for each product websites information.
*
* @return $this
*/
@@ -768,7 +792,7 @@ public function addWebsiteNamesToResult()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function load($printQuery = false, $logQuery = false)
{
@@ -819,14 +843,14 @@ protected function doAddWebsiteNamesToResult()
foreach ($this as $product) {
if (isset($productWebsites[$product->getId()])) {
$product->setData('websites', $productWebsites[$product->getId()]);
+ $product->setData('website_ids', $productWebsites[$product->getId()]);
}
}
return $this;
}
/**
- * Add store availability filter. Include availability product
- * for store website
+ * Add store availability filter. Include availability product for store website.
*
* @param null|string|bool|int|Store $store
* @return $this
@@ -1115,11 +1139,11 @@ public function getSelectCountSql()
/**
* Get SQL for get record count
*
- * @param null $select
+ * @param Select $select
* @param bool $resetLeftJoins
- * @return \Magento\Framework\DB\Select
+ * @return Select
*/
- protected function _getSelectCountSql($select = null, $resetLeftJoins = true)
+ protected function _getSelectCountSql(?Select $select = null, $resetLeftJoins = true)
{
$this->_renderFilters();
$countSelect = $select === null ? $this->_getClearSelect() : $this->_buildClearSelect($select);
@@ -1357,8 +1381,7 @@ public function joinUrlRewrite()
}
/**
- * Add URL rewrites data to product
- * If collection loadded - run processing else set flag
+ * Add URL rewrites data to product. If collection loadded - run processing else set flag.
*
* @param int|string $categoryId
* @return $this
@@ -1412,6 +1435,11 @@ protected function _addUrlRewrite()
['cu' => $this->getTable('catalog_url_rewrite_product_category')],
'u.url_rewrite_id=cu.url_rewrite_id'
)->where('cu.category_id IN (?)', $this->_urlRewriteCategory);
+ } else {
+ $select->joinLeft(
+ ['cu' => $this->getTable('catalog_url_rewrite_product_category')],
+ 'u.url_rewrite_id=cu.url_rewrite_id'
+ )->where('cu.url_rewrite_id IS NULL');
}
// more priority is data with category id
@@ -1518,7 +1546,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
@@ -1581,7 +1609,8 @@ public function addAttributeToFilter($attribute, $condition = null, $joinType =
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
protected function getEntityPkName(\Magento\Eav\Model\Entity\AbstractEntity $entity)
@@ -1792,7 +1821,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']
@@ -1804,7 +1834,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);
@@ -1947,6 +1977,7 @@ protected function _productLimitationPrice($joinLeft = false)
}
// Set additional field filters
foreach ($this->_priceDataFieldFilters as $filterData) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$select->where(call_user_func_array('sprintf', $filterData));
}
} else {
@@ -2000,12 +2031,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']);
}
@@ -2188,7 +2223,7 @@ private function getTierPriceSelect(array $productIds)
$this->getLinkField() . ' IN(?)',
$productIds
)->order(
- $this->getLinkField()
+ 'qty'
);
return $select;
}
@@ -2248,6 +2283,7 @@ private function getBackend()
public function addPriceDataFieldFilter($comparisonFormat, $fields)
{
if (!preg_match('/^%s( (<|>|=|<=|>=|<>) %s)*$/', $comparisonFormat)) {
+ // phpcs:ignore Magento2.Exceptions.DirectThrow
throw new \Exception('Invalid comparison format.');
}
@@ -2343,7 +2379,10 @@ private function getGalleryReadHandler()
}
/**
+ * Retrieve Media gallery resource.
+ *
* @deprecated 101.0.1
+ *
* @return \Magento\Catalog\Model\ResourceModel\Product\Gallery
*/
private function getMediaGalleryResource()
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php
index 7c78dbca5a00..aa6fb8c1f882 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php
@@ -12,6 +12,7 @@
* @author Magento Core Team
* @SuppressWarnings(PHPMD.LongVariable)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @since 100.0.2
*/
class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
@@ -75,7 +76,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param \Magento\Catalog\Model\ResourceModel\Product\Compare\Item $catalogProductCompareItem
* @param \Magento\Catalog\Helper\Product\Compare $catalogProductCompare
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
- *
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -403,6 +403,7 @@ public function clear()
/**
* Retrieve is flat enabled flag
+ *
* Overwrite disable flat for compared item if required EAV resource
*
* @return bool
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php
index 2868392f8528..a9741cd8e1ec 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Model\ResourceModel\Product;
use Magento\Store\Model\Store;
@@ -49,7 +50,8 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
protected function _construct()
@@ -58,7 +60,8 @@ protected function _construct()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
public function getConnection()
@@ -67,6 +70,8 @@ public function getConnection()
}
/**
+ * Load data from table by valueId
+ *
* @param string $tableNameAlias
* @param array $ids
* @param int|null $storeId
@@ -111,6 +116,8 @@ public function loadDataFromTableByValueId(
}
/**
+ * Load product gallery by attributeId
+ *
* @param \Magento\Catalog\Model\Product $product
* @param int $attributeId
* @return array
@@ -132,6 +139,8 @@ public function loadProductGalleryByAttributeId($product, $attributeId)
}
/**
+ * Create base load select
+ *
* @param int $entityId
* @param int $storeId
* @param int $attributeId
@@ -141,7 +150,7 @@ public function loadProductGalleryByAttributeId($product, $attributeId)
*/
protected function createBaseLoadSelect($entityId, $storeId, $attributeId)
{
- $select = $this->createBatchBaseSelect($storeId, $attributeId);
+ $select = $this->createBatchBaseSelect($storeId, $attributeId);
$select = $select->where(
'entity.' . $this->metadata->getLinkField() . ' = ?',
@@ -151,6 +160,8 @@ protected function createBaseLoadSelect($entityId, $storeId, $attributeId)
}
/**
+ * Create batch base select
+ *
* @param int $storeId
* @param int $attributeId
* @return \Magento\Framework\DB\Select
@@ -190,7 +201,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 +212,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 +258,8 @@ protected function removeDuplicates(&$result)
}
/**
+ * Get main table alias
+ *
* @return string
* @since 101.0.0
*/
@@ -249,6 +269,8 @@ public function getMainTableAlias()
}
/**
+ * Bind value to entity
+ *
* @param int $valueId
* @param int $entityId
* @return int
@@ -266,6 +288,8 @@ public function bindValueToEntity($valueId, $entityId)
}
/**
+ * Save data row
+ *
* @param string $table
* @param array $data
* @param array $fields
@@ -355,9 +379,9 @@ public function deleteGalleryValueInStore($valueId, $entityId, $storeId)
$conditions = implode(
' AND ',
[
- $this->getConnection()->quoteInto('value_id = ?', (int) $valueId),
- $this->getConnection()->quoteInto($this->metadata->getLinkField() . ' = ?', (int) $entityId),
- $this->getConnection()->quoteInto('store_id = ?', (int) $storeId)
+ $this->getConnection()->quoteInto('value_id = ?', (int)$valueId),
+ $this->getConnection()->quoteInto($this->metadata->getLinkField() . ' = ?', (int)$entityId),
+ $this->getConnection()->quoteInto('store_id = ?', (int)$storeId)
]
);
@@ -385,7 +409,7 @@ public function duplicate($attributeId, $newFiles, $originalProductId, $newProdu
$select = $this->getConnection()->select()->from(
[$this->getMainTableAlias() => $this->getMainTable()],
- ['value_id', 'value']
+ ['value_id', 'value', 'media_type', 'disabled']
)->joinInner(
['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)],
$this->getMainTableAlias() . '.value_id = entity.value_id',
@@ -402,16 +426,16 @@ public function duplicate($attributeId, $newFiles, $originalProductId, $newProdu
// Duplicate main entries of gallery
foreach ($this->getConnection()->fetchAll($select) as $row) {
- $data = [
- 'attribute_id' => $attributeId,
- 'value' => isset($newFiles[$row['value_id']]) ? $newFiles[$row['value_id']] : $row['value'],
- ];
+ $data = $row;
+ $data['attribute_id'] = $attributeId;
+ $data['value'] = $newFiles[$row['value_id']] ?? $row['value'];
+ unset($data['value_id']);
$valueIdMap[$row['value_id']] = $this->insertGallery($data);
$this->bindValueToEntity($valueIdMap[$row['value_id']], $newProductId);
}
- if (count($valueIdMap) == 0) {
+ if (count($valueIdMap) === 0) {
return [];
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php
index 123f358be40c..77f67480619e 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/AbstractEav.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php
index c33ea7c781aa..e024f0d30f1d 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php
@@ -24,13 +24,11 @@ abstract class AbstractEav extends \Magento\Catalog\Model\ResourceModel\Product\
protected $_eventManager = null;
/**
- * AbstractEav constructor.
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
* @param \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy
* @param \Magento\Eav\Model\Config $eavConfig
* @param \Magento\Framework\Event\ManagerInterface $eventManager
- * @param null $connectionName
- * @param \Magento\Indexer\Model\Indexer\StateFactory|null $stateFactory
+ * @param string $connectionName
*/
public function __construct(
\Magento\Framework\Model\ResourceModel\Db\Context $context,
@@ -70,7 +68,6 @@ public function reindexAll()
/**
* Rebuild index data by entities
*
- *
* @param int|array $processIds
* @return $this
* @throws \Exception
@@ -88,8 +85,8 @@ public function reindexEntities($processIds)
/**
* Rebuild index data by attribute id
- * If attribute is not indexable remove data by attribute
*
+ * If attribute is not indexable remove data by attribute
*
* @param int $attributeId
* @param bool $isIndexable
@@ -245,7 +242,8 @@ protected function _prepareRelationIndex($parentIds = null)
/**
* Retrieve condition for retrieve indexable attribute select
- * the catalog/eav_attribute table must have alias is ca
+ *
+ * The catalog/eav_attribute table must have alias is ca
*
* @return string
*/
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 5b68730209b4..7730d7cc9a7f 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Api\Data\ProductAttributeInterface;
/**
* Catalog Product Eav Select and Multiply Select Attributes Indexer resource model
@@ -24,6 +25,16 @@ class Source extends AbstractEav
*/
protected $_resourceHelper;
+ /**
+ * @var \Magento\Eav\Api\AttributeRepositoryInterface
+ */
+ private $attributeRepository;
+
+ /**
+ * @var \Magento\Framework\Api\SearchCriteriaBuilder
+ */
+ private $criteriaBuilder;
+
/**
* Construct
*
@@ -33,6 +44,8 @@ class Source extends AbstractEav
* @param \Magento\Framework\Event\ManagerInterface $eventManager
* @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper
* @param null|string $connectionName
+ * @param \Magento\Eav\Api\AttributeRepositoryInterface|null $attributeRepository
+ * @param \Magento\Framework\Api\SearchCriteriaBuilder|null $criteriaBuilder
*/
public function __construct(
\Magento\Framework\Model\ResourceModel\Db\Context $context,
@@ -40,7 +53,9 @@ public function __construct(
\Magento\Eav\Model\Config $eavConfig,
\Magento\Framework\Event\ManagerInterface $eventManager,
\Magento\Catalog\Model\ResourceModel\Helper $resourceHelper,
- $connectionName = null
+ $connectionName = null,
+ \Magento\Eav\Api\AttributeRepositoryInterface $attributeRepository = null,
+ \Magento\Framework\Api\SearchCriteriaBuilder $criteriaBuilder = null
) {
parent::__construct(
$context,
@@ -50,6 +65,12 @@ public function __construct(
$connectionName
);
$this->_resourceHelper = $resourceHelper;
+ $this->attributeRepository = $attributeRepository
+ ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\Eav\Api\AttributeRepositoryInterface::class);
+ $this->criteriaBuilder = $criteriaBuilder
+ ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\Framework\Api\SearchCriteriaBuilder::class);
}
/**
@@ -84,7 +105,7 @@ protected function _getIndexableAttributes($multiSelect)
if ($multiSelect == true) {
$select->where('ea.backend_type = ?', 'varchar')->where('ea.frontend_input = ?', 'multiselect');
} else {
- $select->where('ea.backend_type = ?', 'int')->where('ea.frontend_input = ?', 'select');
+ $select->where('ea.backend_type = ?', 'int')->where('ea.frontend_input IN( ? )', ['select', 'boolean']);
}
return $this->getConnection()->fetchCol($select);
@@ -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
*
@@ -330,6 +388,8 @@ public function getIdxTable($table = null)
}
/**
+ * Save data from select
+ *
* @param \Magento\Framework\DB\Select $select
* @param array $options
* @return void
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php
index 47fc6802d7ea..463da8762b7c 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php
@@ -127,6 +127,8 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds =
}
/**
+ * Check if custom options exist.
+ *
* @param IndexTableStructure $priceTable
* @return bool
* @throws \Exception
@@ -154,6 +156,8 @@ private function checkIfCustomOptionsExist(IndexTableStructure $priceTable): boo
}
/**
+ * Get connection.
+ *
* @return \Magento\Framework\DB\Adapter\AdapterInterface
*/
private function getConnection()
@@ -211,7 +215,7 @@ private function getSelectForOptionsWithMultipleValues(string $sourceTable): Sel
} else {
$select->joinLeft(
['otps' => $this->getTable('catalog_product_option_type_price')],
- 'otps.option_type_id = otpd.option_type_id AND otpd.store_id = cwd.default_store_id',
+ 'otps.option_type_id = otpd.option_type_id AND otps.store_id = cwd.default_store_id',
[]
);
@@ -373,6 +377,8 @@ private function getSelectAggregated(string $sourceTable): Select
}
/**
+ * Get select for update.
+ *
* @param string $sourceTable
* @return \Magento\Framework\DB\Select
*/
@@ -402,6 +408,8 @@ private function getSelectForUpdate(string $sourceTable): Select
}
/**
+ * Get table name.
+ *
* @param string $tableName
* @return string
*/
@@ -411,6 +419,8 @@ private function getTable(string $tableName): string
}
/**
+ * Is price scope global.
+ *
* @return bool
*/
private function isPriceGlobal(): bool
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
index 168fa8f50acc..3b4c3408e742 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
@@ -10,6 +10,7 @@
/**
* Default Product Type Price Indexer Resource model
+ *
* For correctly work need define product type id
*
* @api
@@ -208,6 +209,8 @@ public function reindexEntity($entityIds)
}
/**
+ * Reindex prices.
+ *
* @param null|int|array $entityIds
* @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice
*/
@@ -604,7 +607,7 @@ protected function _applyCustomOption()
[]
)->joinLeft(
['otps' => $this->getTable('catalog_product_option_type_price')],
- 'otps.option_type_id = otpd.option_type_id AND otpd.store_id = cs.store_id',
+ 'otps.option_type_id = otpd.option_type_id AND otps.store_id = cs.store_id',
[]
)->group(
['i.entity_id', 'i.customer_group_id', 'i.website_id', 'o.option_id']
@@ -802,6 +805,8 @@ public function getIdxTable($table = null)
}
/**
+ * Check if product exists.
+ *
* @return bool
*/
protected function hasEntity()
@@ -823,6 +828,8 @@ protected function hasEntity()
}
/**
+ * Get total tier price expression.
+ *
* @param \Zend_Db_Expr $priceExpression
* @return \Zend_Db_Expr
*/
@@ -863,6 +870,8 @@ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression)
}
/**
+ * Get tier price expression for table.
+ *
* @param string $tableAlias
* @param \Zend_Db_Expr $priceExpression
* @return \Zend_Db_Expr
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php
index 0005ac8dea58..95fecc832fa2 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php
@@ -16,6 +16,7 @@
/**
* Prepare base select for Product Price index limited by specified dimensions: website and customer group
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class BaseFinalPrice
@@ -66,10 +67,11 @@ class BaseFinalPrice
private $metadataPool;
/**
- * BaseFinalPrice constructor.
* @param \Magento\Framework\App\ResourceConnection $resource
* @param JoinAttributeProcessor $joinAttributeProcessor
* @param \Magento\Framework\Module\Manager $moduleManager
+ * @param \Magento\Framework\Event\ManagerInterface $eventManager
+ * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
* @param string $connectionName
*/
public function __construct(
@@ -89,6 +91,8 @@ public function __construct(
}
/**
+ * Build query for base final price.
+ *
* @param Dimension[] $dimensions
* @param string $productType
* @param array $entityIds
@@ -285,7 +289,7 @@ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression)
/**
* Get tier price expression for table
*
- * @param $tableAlias
+ * @param string $tableAlias
* @param \Zend_Db_Expr $priceExpression
* @return \Zend_Db_Expr
*/
@@ -305,7 +309,7 @@ private function getTierPriceExpressionForTable($tableAlias, \Zend_Db_Expr $pric
/**
* Get connection
*
- * return \Magento\Framework\DB\Adapter\AdapterInterface
+ * @return \Magento\Framework\DB\Adapter\AdapterInterface
* @throws \DomainException
*/
private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/TemporaryTableStrategy.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/TemporaryTableStrategy.php
index 54673cb01bb1..89daab288597 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/TemporaryTableStrategy.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/TemporaryTableStrategy.php
@@ -30,7 +30,7 @@ class TemporaryTableStrategy implements \Magento\Framework\Indexer\Table\Strateg
/**
* TemporaryTableStrategy constructor.
- * @param \Magento\Framework\Indexer\Table\Strategy $strategy
+ * @param \Magento\Framework\Indexer\Table\StrategyInterface $strategy
* @param \Magento\Framework\App\ResourceConnection $resource
*/
public function __construct(
@@ -66,9 +66,10 @@ public function getTableName($tablePrefix)
}
/**
- * Create temporary index table based on memory table
+ * Create temporary index table based on memory table{@inheritdoc}
*
- * {@inheritdoc}
+ * @param string $tablePrefix
+ * @return string
*/
public function prepareTableName($tablePrefix)
{
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/DeleteHandler.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/DeleteHandler.php
index 024c87c9fc88..a554ff2641df 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/DeleteHandler.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/DeleteHandler.php
@@ -60,9 +60,11 @@ public function __construct(
}
/**
+ * Delete linked product.
+ *
* @param string $entityType
* @param object $entity
- * @return object
+ * @return void
* @throws CouldNotDeleteException
* @throws NoSuchEntityException
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
index 8841b6059c46..841fe17bdcf0 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
@@ -11,6 +11,9 @@
use Magento\Framework\DB\Select;
use Magento\Store\Model\Store;
+/**
+ * Provide Select object for retrieve product id with minimal price.
+ */
class LinkedProductSelectBuilderByBasePrice implements LinkedProductSelectBuilderInterface
{
/**
@@ -69,7 +72,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function build($productId)
{
@@ -85,7 +88,7 @@ public function build($productId)
[]
)->joinInner(
[BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable],
- sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS, $linkField),
+ sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
['entity_id']
)->joinInner(
['t' => $priceAttribute->getBackendTable()],
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php
index ce0a9b6e461c..494dbac02d79 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php
@@ -17,11 +17,12 @@
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
+use Magento\Catalog\Helper\Data;
/**
* Catalog product custom option resource model
*
- * @author Magento Core Team
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Value extends AbstractDb
{
@@ -51,6 +52,11 @@ class Value extends AbstractDb
*/
private $localeFormat;
+ /**
+ * @var Data
+ */
+ private $dataHelper;
+
/**
* Class constructor
*
@@ -59,17 +65,21 @@ class Value extends AbstractDb
* @param StoreManagerInterface $storeManager
* @param ScopeConfigInterface $config
* @param string $connectionName
+ * @param Data $dataHelper
*/
public function __construct(
Context $context,
CurrencyFactory $currencyFactory,
StoreManagerInterface $storeManager,
ScopeConfigInterface $config,
- $connectionName = null
+ $connectionName = null,
+ Data $dataHelper = null
) {
$this->_currencyFactory = $currencyFactory;
$this->_storeManager = $storeManager;
$this->_config = $config;
+ $this->dataHelper = $dataHelper ?: ObjectManager::getInstance()
+ ->get(Data::class);
parent::__construct($context, $connectionName);
}
@@ -85,6 +95,7 @@ protected function _construct()
/**
* Proceed operations after object is saved
+ *
* Save options store data
*
* @param AbstractModel $object
@@ -130,7 +141,7 @@ protected function _saveValuePrices(AbstractModel $object)
$optionTypeId = $this->getConnection()->fetchOne($select);
if ($optionTypeId) {
- if ($object->getStoreId() == '0') {
+ if ($object->getStoreId() == '0' || $this->dataHelper->isPriceGlobal()) {
$bind = ['price' => $price, 'price_type' => $priceType];
$where = [
'option_type_id = ?' => $optionTypeId,
@@ -160,19 +171,22 @@ protected function _saveValuePrices(AbstractModel $object)
&& isset($objectPrice)
&& $object->getStoreId() != Store::DEFAULT_STORE_ID
) {
- $baseCurrency = $this->_config->getValue(
+ $website = $this->_storeManager->getStore($object->getStoreId())->getWebsite();
+
+ $websiteBaseCurrency = $this->_config->getValue(
Currency::XML_PATH_CURRENCY_BASE,
- 'default'
+ ScopeInterface::SCOPE_WEBSITE,
+ $website
);
- $storeIds = $this->_storeManager->getStore($object->getStoreId())->getWebsite()->getStoreIds();
+ $storeIds = $website->getStoreIds();
if (is_array($storeIds)) {
foreach ($storeIds as $storeId) {
if ($priceType == 'fixed') {
$storeCurrency = $this->_storeManager->getStore($storeId)->getBaseCurrencyCode();
/** @var $currencyModel Currency */
$currencyModel = $this->_currencyFactory->create();
- $currencyModel->load($baseCurrency);
+ $currencyModel->load($websiteBaseCurrency);
$rate = $currencyModel->getRate($storeCurrency);
if (!$rate) {
$rate = 1;
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php
index c7829ab3a31d..c5c656b72652 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php
@@ -13,6 +13,7 @@
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Store\Api\StoreResolverInterface;
use Magento\Store\Model\Store;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class StatusBaseSelectProcessor
@@ -30,28 +31,32 @@ class StatusBaseSelectProcessor implements BaseSelectProcessorInterface
private $metadataPool;
/**
- * @var StoreResolverInterface
+ * @var StoreManagerInterface
*/
- private $storeResolver;
+ private $storeManager;
/**
* @param Config $eavConfig
* @param MetadataPool $metadataPool
* @param StoreResolverInterface $storeResolver
+ * @param StoreManagerInterface $storeManager
+ *
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
Config $eavConfig,
MetadataPool $metadataPool,
- StoreResolverInterface $storeResolver
+ StoreResolverInterface $storeResolver,
+ StoreManagerInterface $storeManager = null
) {
$this->eavConfig = $eavConfig;
$this->metadataPool = $metadataPool;
- $this->storeResolver = $storeResolver;
+ $this->storeManager = $storeManager ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(StoreManagerInterface::class);
}
/**
- * @param Select $select
- * @return Select
+ * @inheritdoc
*/
public function process(Select $select)
{
@@ -70,7 +75,7 @@ public function process(Select $select)
['status_attr' => $statusAttribute->getBackendTable()],
"status_attr.{$linkField} = " . self::PRODUCT_TABLE_ALIAS . ".{$linkField}"
. ' AND status_attr.attribute_id = ' . (int)$statusAttribute->getAttributeId()
- . ' AND status_attr.store_id = ' . $this->storeResolver->getCurrentStoreId(),
+ . ' AND status_attr.store_id = ' . $this->storeManager->getStore()->getId(),
[]
);
diff --git a/app/code/Magento/Catalog/Model/Rss/Category.php b/app/code/Magento/Catalog/Model/Rss/Category.php
index a58569d1b59d..653d86b177a5 100644
--- a/app/code/Magento/Catalog/Model/Rss/Category.php
+++ b/app/code/Magento/Catalog/Model/Rss/Category.php
@@ -6,8 +6,7 @@
namespace Magento\Catalog\Model\Rss;
/**
- * Class Category
- * @package Magento\Catalog\Model\Rss
+ * Rss Category model.
*/
class Category
{
@@ -42,9 +41,11 @@ public function __construct(
}
/**
+ * Get products for given category.
+ *
* @param \Magento\Catalog\Model\Category $category
* @param int $storeId
- * @return $this
+ * @return \Magento\Catalog\Model\ResourceModel\Product\Collection
*/
public function getProductCollection(\Magento\Catalog\Model\Category $category, $storeId)
{
diff --git a/app/code/Magento/Catalog/Model/Template/Filter.php b/app/code/Magento/Catalog/Model/Template/Filter.php
index 1eb30ff95a40..8cd61415b958 100644
--- a/app/code/Magento/Catalog/Model/Template/Filter.php
+++ b/app/code/Magento/Catalog/Model/Template/Filter.php
@@ -66,7 +66,7 @@ public function __construct(
* Set use absolute links flag
*
* @param bool $flag
- * @return \Magento\Email\Model\Template\Filter
+ * @return $this
*/
public function setUseAbsoluteLinks($flag)
{
@@ -76,10 +76,11 @@ public function setUseAbsoluteLinks($flag)
/**
* Setter whether SID is allowed in store directive
+ *
* Doesn't set anything intentionally, since SID is not allowed in any kind of emails
*
* @param bool $flag
- * @return \Magento\Email\Model\Template\Filter
+ * @return $this
*/
public function setUseSessionInUrl($flag)
{
@@ -132,6 +133,7 @@ public function mediaDirective($construction)
/**
* Retrieve store URL directive
+ *
* Support url and direct_url properties
*
* @param array $construction
diff --git a/app/code/Magento/Catalog/Observer/CategoryProductIndexer.php b/app/code/Magento/Catalog/Observer/CategoryProductIndexer.php
new file mode 100644
index 000000000000..ca87efaa8749
--- /dev/null
+++ b/app/code/Magento/Catalog/Observer/CategoryProductIndexer.php
@@ -0,0 +1,42 @@
+processor = $processor;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(Observer $observer): void
+ {
+ $productIds = $observer->getEvent()->getProductIds();
+ if (!empty($productIds) && $this->processor->isIndexerScheduled()) {
+ $this->processor->markIndexerAsInvalid();
+ }
+ }
+}
diff --git a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php
index a597b8fddda9..ed9f89efc689 100644
--- a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php
+++ b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php
@@ -3,9 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Observer;
use Magento\Framework\Event\ObserverInterface;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
* Set value for Special Price start date
@@ -13,21 +16,20 @@
class SetSpecialPriceStartDate implements ObserverInterface
{
/**
- * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
+ * @var TimezoneInterface
*/
private $localeDate;
/**
- * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
- * @codeCoverageIgnore
+ * @param TimezoneInterface $localeDate
*/
- public function __construct(\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate)
+ public function __construct(TimezoneInterface $localeDate)
{
$this->localeDate = $localeDate;
}
/**
- * Set the current date to Special Price From attribute if it empty
+ * Set the current date to Special Price From attribute if it's empty.
*
* @param \Magento\Framework\Event\Observer $observer
* @return $this
@@ -36,8 +38,8 @@ public function execute(\Magento\Framework\Event\Observer $observer)
{
/** @var $product \Magento\Catalog\Model\Product */
$product = $observer->getEvent()->getProduct();
- if ($product->getSpecialPrice() && !$product->getSpecialFromDate()) {
- $product->setData('special_from_date', $this->localeDate->date());
+ if ($product->getSpecialPrice() && ! $product->getSpecialFromDate()) {
+ $product->setData('special_from_date', $this->localeDate->date()->setTime(0, 0));
}
return $this;
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 000000000000..6add542b1555
--- /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/Plugin/Model/Attribute/Backend/AttributeValidation.php b/app/code/Magento/Catalog/Plugin/Model/Attribute/Backend/AttributeValidation.php
index 597a1466a125..eca4d468950e 100644
--- a/app/code/Magento/Catalog/Plugin/Model/Attribute/Backend/AttributeValidation.php
+++ b/app/code/Magento/Catalog/Plugin/Model/Attribute/Backend/AttributeValidation.php
@@ -7,6 +7,9 @@
use Magento\Store\Model\Store;
+/**
+ * Attribute validation
+ */
class AttributeValidation
{
/**
@@ -14,6 +17,11 @@ class AttributeValidation
*/
private $storeManager;
+ /**
+ * @var array
+ */
+ private $allowedEntityTypes;
+
/**
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param array $allowedEntityTypes
@@ -27,9 +35,12 @@ public function __construct(
}
/**
+ * Around validate
+ *
* @param \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend $subject
* @param \Closure $proceed
* @param \Magento\Framework\DataObject $entity
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
* @return bool
*/
public function aroundValidate(
@@ -41,7 +52,7 @@ public function aroundValidate(
return $entity instanceof $allowedEntity;
}, $this->allowedEntityTypes)));
- if ($isAllowedType && $this->storeManager->getStore()->getId() !== Store::DEFAULT_STORE_ID) {
+ if ($isAllowedType && (int) $this->storeManager->getStore()->getId() !== Store::DEFAULT_STORE_ID) {
$attrCode = $subject->getAttribute()->getAttributeCode();
// Null is meaning "no value" which should be overridden by value from default scope
if (array_key_exists($attrCode, $entity->getData()) && $entity->getData($attrCode) === null) {
diff --git a/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php b/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php
new file mode 100644
index 000000000000..dd750cfbc696
--- /dev/null
+++ b/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php
@@ -0,0 +1,56 @@
+productRepository = $productRepository;
+ }
+
+ /**
+ * Update product 'has_options' and 'required_options' attributes after option save
+ *
+ * @param \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $subject
+ * @param \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option
+ *
+ * @return \Magento\Catalog\Api\Data\ProductCustomOptionInterface
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterSave(
+ \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $subject,
+ \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option
+ ) {
+ $product = $this->productRepository->get($option->getProductSku());
+ if (!$product->getHasOptions() ||
+ ($option->getIsRequire() && !$product->getRequiredOptions())) {
+ $product->setCanSaveCustomOptions(true);
+ $product->setOptionsSaved(true);
+ $currentOptions = array_filter($product->getOptions(), function ($iOption) use ($option) {
+ return $option->getOptionId() != $iOption->getOptionId();
+ });
+ $currentOptions[] = $option;
+ $product->setOptions($currentOptions);
+ $product->save();
+ }
+
+ return $option;
+ }
+}
diff --git a/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php b/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php
index dfa06b6ebe6c..b942f5570f57 100644
--- a/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php
+++ b/app/code/Magento/Catalog/Plugin/Model/ResourceModel/Config.php
@@ -8,6 +8,9 @@
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Serialize\SerializerInterface;
+/**
+ * Config cache plugin.
+ */
class Config
{
/**#@+
@@ -46,8 +49,10 @@ public function __construct(
}
/**
+ * Cache attribute used in listing.
+ *
* @param \Magento\Catalog\Model\ResourceModel\Config $config
- * @param callable $proceed
+ * @param \Closure $proceed
* @return array
*/
public function aroundGetAttributesUsedInListing(
@@ -73,8 +78,10 @@ public function aroundGetAttributesUsedInListing(
}
/**
+ * Cache attributes used for sorting.
+ *
* @param \Magento\Catalog\Model\ResourceModel\Config $config
- * @param callable $proceed
+ * @param \Closure $proceed
* @return array
*/
public function aroundGetAttributesUsedForSortBy(
diff --git a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php
index 54a13be864db..77368517a315 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/Pricing/Price/MinimalTierPriceCalculator.php b/app/code/Magento/Catalog/Pricing/Price/MinimalTierPriceCalculator.php
index 387ef9416ef6..a5e573caa381 100644
--- a/app/code/Magento/Catalog/Pricing/Price/MinimalTierPriceCalculator.php
+++ b/app/code/Magento/Catalog/Pricing/Price/MinimalTierPriceCalculator.php
@@ -29,8 +29,10 @@ public function __construct(CalculatorInterface $calculator)
}
/**
- * Get raw value of "as low as" as a minimal among tier prices
- * {@inheritdoc}
+ * Get raw value of "as low as" as a minimal among tier prices{@inheritdoc}
+ *
+ * @param SaleableInterface $saleableItem
+ * @return float|null
*/
public function getValue(SaleableInterface $saleableItem)
{
@@ -49,8 +51,10 @@ public function getValue(SaleableInterface $saleableItem)
}
/**
- * Return calculated amount object that keeps "as low as" value
- * {@inheritdoc}
+ * Return calculated amount object that keeps "as low as" value{@inheritdoc}
+ *
+ * @param SaleableInterface $saleableItem
+ * @return AmountInterface|null
*/
public function getAmount(SaleableInterface $saleableItem)
{
@@ -58,6 +62,6 @@ public function getAmount(SaleableInterface $saleableItem)
return $value === null
? null
- : $this->calculator->getAmount($value, $saleableItem);
+ : $this->calculator->getAmount($value, $saleableItem, 'tax');
}
}
diff --git a/app/code/Magento/Catalog/Pricing/Price/RegularPrice.php b/app/code/Magento/Catalog/Pricing/Price/RegularPrice.php
index 1397ceb6bf71..2c4e332e7123 100644
--- a/app/code/Magento/Catalog/Pricing/Price/RegularPrice.php
+++ b/app/code/Magento/Catalog/Pricing/Price/RegularPrice.php
@@ -29,7 +29,7 @@ public function getValue()
if ($this->value === null) {
$price = $this->product->getPrice();
$priceInCurrentCurrency = $this->priceCurrency->convertAndRound($price);
- $this->value = $priceInCurrentCurrency ? floatval($priceInCurrentCurrency) : 0;
+ $this->value = $priceInCurrentCurrency ? (float)$priceInCurrentCurrency : 0;
}
return $this->value;
}
diff --git a/app/code/Magento/Catalog/Pricing/Price/SpecialPrice.php b/app/code/Magento/Catalog/Pricing/Price/SpecialPrice.php
index b1bfc6ff4ad6..77c48fdb1667 100644
--- a/app/code/Magento/Catalog/Pricing/Price/SpecialPrice.php
+++ b/app/code/Magento/Catalog/Pricing/Price/SpecialPrice.php
@@ -11,6 +11,7 @@
use Magento\Framework\Pricing\Price\AbstractPrice;
use Magento\Framework\Pricing\Price\BasePriceProviderInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Api\Data\WebsiteInterface;
/**
* Special price model
@@ -46,6 +47,8 @@ public function __construct(
}
/**
+ * Retrieve special price.
+ *
* @return bool|float
*/
public function getValue()
@@ -96,19 +99,19 @@ public function getSpecialToDate()
}
/**
- * @return bool
+ * @inheritdoc
*/
public function isScopeDateInInterval()
{
return $this->localeDate->isScopeDateInInterval(
- $this->product->getStore(),
+ WebsiteInterface::ADMIN_CODE,
$this->getSpecialFromDate(),
$this->getSpecialToDate()
);
}
/**
- * @return bool
+ * @inheritdoc
*/
public function isPercentageDiscount()
{
diff --git a/app/code/Magento/Catalog/Pricing/Price/TierPrice.php b/app/code/Magento/Catalog/Pricing/Price/TierPrice.php
index 74f98c2e66a5..f250927889c2 100644
--- a/app/code/Magento/Catalog/Pricing/Price/TierPrice.php
+++ b/app/code/Magento/Catalog/Pricing/Price/TierPrice.php
@@ -80,7 +80,7 @@ public function __construct(
GroupManagementInterface $groupManagement,
CustomerGroupRetrieverInterface $customerGroupRetriever = null
) {
- $quantity = floatval($quantity) ? $quantity : 1;
+ $quantity = (float)$quantity ? $quantity : 1;
parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency);
$this->customerSession = $customerSession;
$this->groupManagement = $groupManagement;
diff --git a/app/code/Magento/Catalog/Setup/CategorySetup.php b/app/code/Magento/Catalog/Setup/CategorySetup.php
index 271387932829..f8542454bef9 100644
--- a/app/code/Magento/Catalog/Setup/CategorySetup.php
+++ b/app/code/Magento/Catalog/Setup/CategorySetup.php
@@ -10,7 +10,6 @@
use Magento\Catalog\Block\Adminhtml\Category\Helper\Pricestep;
use Magento\Catalog\Block\Adminhtml\Category\Helper\Sortby\Available;
use Magento\Catalog\Block\Adminhtml\Category\Helper\Sortby\DefaultSortby;
-use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\BaseImage;
use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Category as CategoryFormHelper;
use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Weight as WeightFormHelper;
use Magento\Catalog\Model\Attribute\Backend\Customlayoutupdate;
@@ -54,6 +53,8 @@
use Magento\Theme\Model\Theme\Source\Theme;
/**
+ * Setup category with default entities.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CategorySetup extends EavSetup
@@ -593,7 +594,6 @@ public function getDefaultEntities()
'label' => 'Base Image',
'input' => 'media_image',
'frontend' => ImageFrontendModel::class,
- 'input_renderer' => BaseImage::class,
'required' => false,
'sort_order' => 0,
'global' => ScopedAttributeInterface::SCOPE_STORE,
@@ -626,7 +626,6 @@ public function getDefaultEntities()
'type' => 'varchar',
'label' => 'Media Gallery',
'input' => 'gallery',
- 'backend' => Media::class,
'required' => false,
'sort_order' => 4,
'group' => 'Images',
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
index 692487c1d60c..a544be434f9c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
@@ -11,9 +11,13 @@
-
+
-
-
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml
new file mode 100644
index 000000000000..90ceb1e4a1f9
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssignImageRolesActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
index 76f65381f43f..90d732c9654e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
@@ -154,7 +154,7 @@
-
+
@@ -231,14 +231,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml
new file mode 100644
index 000000000000..dd66919640a7
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml
new file mode 100644
index 000000000000..fe859fab5266
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminOpenNewProductFormPageActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index 84231473b685..3c44a8f1898a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -15,12 +15,20 @@
-
+
+
+
+
+
+
+
+
+
-
+
@@ -34,6 +42,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -70,10 +97,21 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -159,7 +197,7 @@
-
+
@@ -173,6 +211,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -198,10 +274,29 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -214,4 +309,178 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{amount}}
+ $grabProductTierPriceInput
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
index fd8083869206..86158aba68f8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
@@ -13,8 +13,255 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
index 5948ca12dcf0..019d103a31cf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
@@ -45,4 +45,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
index 1bd9bb4a09c8..ad32b8edbd24 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
@@ -144,6 +144,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -155,6 +167,7 @@
+
@@ -164,6 +177,13 @@
+
+
+
+
+
+
+
@@ -172,8 +192,7 @@
-
-
+
@@ -182,8 +201,20 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -206,4 +237,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml
new file mode 100644
index 000000000000..9402ac05d79a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml
new file mode 100644
index 000000000000..25390e6b4a80
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
new file mode 100644
index 000000000000..7917fe68aaeb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
new file mode 100644
index 000000000000..963c9d9f1c91
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
new file mode 100644
index 000000000000..f7cd2e707628
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml
similarity index 86%
rename from app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml
rename to app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml
index 19de3e859ae9..53de47f81060 100644
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CreateNewProductActionGroup.xml
@@ -5,10 +5,10 @@
* See COPYING.txt for license details.
*/
-->
+
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
-
@@ -22,4 +22,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
index 7373d5baea0c..b914d5e20712 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
@@ -15,7 +15,6 @@
-
@@ -48,10 +47,56 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
diff --git a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
similarity index 85%
rename from app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
rename to app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
index 724c6d92846c..7491b39aa8f2 100644
--- a/app/code/Magento/Braintree/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductActionGroup.xml
@@ -5,8 +5,9 @@
* See COPYING.txt for license details.
*/
-->
+
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
@@ -23,4 +24,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml
new file mode 100644
index 000000000000..575cbdcd9b6d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
index 8f89a85e1489..ea2543cd5c2a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml
new file mode 100644
index 000000000000..31b024c82a9a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml
new file mode 100644
index 000000000000..67cdd8192fcb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
index 113e108577aa..aca9ba24c116 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
@@ -12,10 +12,24 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml
new file mode 100644
index 000000000000..1bb7c179dfca
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml
new file mode 100644
index 000000000000..c25b73bab21a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductInWidgetActionGroup.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml
new file mode 100644
index 000000000000..6cb156723b28
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml
new file mode 100644
index 000000000000..3c62ef89e584
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml
new file mode 100644
index 000000000000..85d3927a6d6d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
index c980c43b8f3a..4c7c011028c9 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/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml
new file mode 100644
index 000000000000..fb2065d228d5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartOnProductPageActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml
new file mode 100644
index 000000000000..f5fabae5fc4c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
new file mode 100644
index 000000000000..aec21f3bc48c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml
new file mode 100644
index 000000000000..24e1fe9cf5ec
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/AdminMenuData.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+ Catalog
+ Catalog
+ magento-catalog-catalog
+
+
+ Default Category (ID: 2)
+ Categories
+ magento-catalog-catalog-categories
+
+
+ Products
+ Products
+ magento-catalog-catalog-products
+
+
+ Attribute Sets
+ Attribute Set
+ magento-catalog-catalog-attributes-sets
+
+
+ Product Attributes
+ Product
+ magento-catalog-catalog-attributes-attributes
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/AttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/AttributeSetData.xml
new file mode 100644
index 000000000000..6e1b25fb9cdc
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/AttributeSetData.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ 4
+ Default
+ 0
+
+
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 000000000000..d78c03a51dd7
--- /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/CatalogPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml
new file mode 100644
index 000000000000..0f7f4da1b68c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+ scopeWebsite
+ defaultProductPrice
+
+
+ 1
+
+
+ 0
+
+
+
+ scopeGlobal
+ defaultProductPrice
+
+
+ 0
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogRecentlyProductsConfigData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogRecentlyProductsConfigData.xml
new file mode 100644
index 000000000000..d1e469deaebb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogRecentlyProductsConfigData.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+ EnableCatalogRecentlyProductsSynchronize
+
+
+
+ 1
+
+
+
+ DefaultCatalogRecentlyProductsSynchronize
+
+
+
+ 0
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml
new file mode 100644
index 000000000000..31783526932b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ 99.99
+ 0
+
+
+
+ 55.55
+ 0
+
+
+
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 000000000000..abf01f00dbbc
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogStorefrontConfigData.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+ GridPerPageValues
+ RememberCategoryPagination
+
+
+
+ 9,12,20,24
+
+
+
+ 1
+
+
+
+ DefaultCatalogStorefrontFlagZero
+ DefaultListAllowAll
+ DefaultFlatCatalogProduct
+
+
+
+ 0
+
+
+
+ 0
+
+
+
+ 0
+
+
+
+ UseFlatCatalogProduct
+ UseFlatCatalogCategory
+
+
+
+ 1
+
+
+
+ 1
+
+
+
+ DefaultFlatCatalogProduct
+ DefaultFlatCatalogCategory
+
+
+
+ 0
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
index 5c79c321c943..27167d03d528 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
@@ -42,4 +42,72 @@
true
+
+ FirstLevelSubCategory
+ firstlevelsubcategory
+
+
+ SecondLevelSubCategory
+ secondlevelsubcategory
+
+
+ ThirdLevelSubCategory
+ subcategory
+
+
+ FourthLevelSubCategory
+ subcategory
+
+
+ FifthLevelCategory
+ category
+
+
+ SimpleRootSubCategory
+ simplerootsubcategory
+ true
+ true
+ simplerootsubcategory
+
+
+
+ SubCategory
+ subcategory
+ true
+ true
+
+
+ SecondLevel
+ secondlevel
+ secondlevel
+ true
+ true
+
+
+
+ ThirdLevel
+ thirdlevel
+ thirdlevel
+ true
+ true
+
+
+
+ NotInclMenu
+ notinclemenu
+ true
+ false
+
+
+ NotActive
+ notactive
+ false
+ true
+
+
+ InactiveNotInMenu
+ inactivenotinmenu
+ false
+ false
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
index 8a26b6babdbb..d09880f14afb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
@@ -13,4 +13,8 @@
1
2
+
+ "Pursuit Lumaflex™ Tone Band"
+ "x™"
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
index a46d40c62c76..a2bdaa7dbc62 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 000000000000..a2391dda5480
--- /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/NewProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/NewProductData.xml
new file mode 100644
index 000000000000..4479805cb12f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/NewProductData.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ ProductTest
+ 100
+ 100
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
index b367cdcab9d8..817dd637f81d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
@@ -52,6 +52,41 @@
true
ProductAttributeFrontendLabel
+
+ test_attr_
+ select
+ global
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ ProductAttributeFrontendLabel
+
+
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+
attribute
select
@@ -73,6 +108,27 @@
true
ProductAttributeFrontendLabel
+
+ attribute
+ select
+ global
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
testattribute
select
@@ -115,4 +171,153 @@
true
ProductAttributeFrontendLabel
+
+ attribute
+ multiselect
+ global
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
+
+ news_from_date
+ Set Product as New from Date
+ date
+ false
+ true
+ website
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ true
+ true
+ false
+ ProductAttributeFrontendLabel
+
+
+ attribute
+ text
+ global
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
+
+ attribute
+ boolean
+ global
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
+
+ attribute
+ text
+ global
+ false
+ false
+ false
+ true
+ text
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ ProductAttributeFrontendLabel
+
+
+ text
+ defaultValue
+ No
+
+
+ date
+ No
+
+
+ date
+ No
+
+
+ select
+ Dropdown
+ No
+ opt1Admin
+ opt1Front
+ opt2Admin
+ opt2Front
+ opt3Admin
+ opt3Front
+
+
+ multiselect
+ Multiple Select
+ No
+ opt1Admin
+ opt1Front
+ opt2Admin
+ opt2Front
+ opt3Admin
+ opt3Front
+
+
+ select
+ Dropdown
+ No
+ opt1'Admin
+ opt1'Front
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
index c575f1a5db82..fcb56cf298a9 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
@@ -65,4 +65,25 @@
false
0
+
+
+ Green
+ false
+ 3
+ Option7Store0
+ Option8Store1
+
+
+
+ Red
+ false
+ 3
+ Option9Store0
+ Option10Store1
+
+
+
+ White
+ white
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml
index 68f51559a9f3..6d4314a6d865 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml
@@ -14,4 +14,16 @@
7
0
+
+
+ 4
+ 7
+ 1
+
+
+
+ 0
+ 0
+ 0
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 9ae551b69d38..b8d6ec8e63e7 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
@@ -34,6 +35,13 @@
EavStockItem
CustomAttributeCategoryIds
+
+ Pursuit Lumaflex™ Tone Band
+ x™
+
+
+ 100
+
ApiProductDescription
ApiProductShortDescription
@@ -56,6 +64,48 @@
EavStockItem
CustomAttributeCategoryIds
+
+ SimpleProductForTest1
+ simple
+ 4
+ SimpleProductAfterImport1
+ 250.00
+ 4
+ 1
+ 100
+ simple-product-for-test-1
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ SimpleProductForTest2
+ simple
+ 4
+ SimpleProductAfterImport2
+ 300.00
+ 4
+ 1
+ 100
+ simple-product-for-test-2
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ SimpleProductForTest3
+ simple
+ 4
+ SimpleProductAfterImport3
+ 350.00
+ 4
+ 1
+ 100
+ simple-product-for-test-3
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
SimpleProduct
simple
@@ -92,6 +142,31 @@
0
CustomAttributeCategoryIds
+
+ testSku
+ simple
+ 4
+ 4
+ OutOfStockProduct
+ 123.00
+ testurlkey
+ 1
+ 0
+
+
+
+ testSku
+ simple
+ 4
+ 4
+ SimpleOffline
+ 123.00
+ 2
+ 100
+ testurlkey
+ EavStockItem
+ CustomAttributeProductAttribute
+
321.00
@@ -106,6 +181,18 @@
EavStockItem
CustomAttributeProductAttribute
+
+ api-simple-product
+ simple
+ 4
+ 4
+ Api Simple Out Of Stock Product
+ 123.00
+ api-simple-product
+ 1
+ 100
+ CustomAttributeProductAttribute
+
api-simple-product
simple
@@ -145,6 +232,15 @@
EavStockItem
CustomAttributeProductAttribute
+
+ 50
+
+
+ 60
+
+
+ 70
+
api-simple-product-two
simple
@@ -180,6 +276,9 @@
EavStockItem
CustomAttributeProductUrlKey
+
+ 1
+
Image1
1.00
@@ -206,6 +305,15 @@
magento-again
jpg
+
+ magento-adobe
+ 1.00
+ Upload File
+ Yes
+ adobe-base.jpg
+ adobe-base
+ jpg
+
霁产品
simple
@@ -256,7 +364,7 @@
1
100
0
- EavStockItem
+ EavStock100
CustomAttributeCategoryIds
@@ -301,6 +409,20 @@
EavStockItem
CustomAttributeCategoryIds
+
+ testSku
+ simple
+
+ 4
+ testProductName
+ 123.00
+ testurlkey
+ 1
+ 1
+ 100
+ EavStockItem
+ CustomAttributeCategoryIds
+
magento.jpg
@@ -319,6 +441,11 @@
ProductOptionDropDownWithLongValuesTitle
+
+
+ ProductOptionField
+ ProductOptionArea
+
api-virtual-product
virtual
@@ -419,4 +546,453 @@
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
+
+
+ virtualProduct
+ virtualsku
+ 10
+ Catalog, Search
+ virtualproduct
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 100.00
+ None
+ 999
+ In Stock
+ Catalog, Search
+ virtual-product
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 100.00
+ None
+ 999
+ In Stock
+ Catalog, Search
+ virtual-product
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 9,000.00
+ 999
+ In Stock
+ Catalog, Search
+ virtual-product
+ IN STOCK
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 100.00
+ 999
+ virtual-product
+ 90.00
+ IN STOCK
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 9,000.00
+ 999
+ Out of Stock
+ Catalog, Search
+ virtual-product
+ OUT OF STOCK
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 10.00
+ 999
+ virtual-product
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 120.00
+ None
+ 999
+ In Stock
+ IN STOCK
+ Search
+ virtual-product
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 99.99
+ None
+ 999
+ In Stock
+ IN STOCK
+ Catalog
+ virtual-product
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 99.99
+ Taxable Goods
+ Out of Stock
+ OUT OF STOCK
+ Search
+ virtual-product
+ virtual
+
+
+ Testp
+ testsku
+ simple
+ 4
+ 4
+ 560.00
+ testurl-
+ 1
+ 25
+ 1
+ EavStock100
+
+
+ Product With Long Name And Sku - But not too long
+ Product With Long Name And Sku - But not too long
+
+
+ pagi
+ pagisku
+ simple
+ 4
+ 4
+ 780.00
+ pagiurl-
+ 1
+ 50
+ 5
+ EavStock100
+
+
+ Magento3
+ 1.00
+ Upload File
+ Yes
+ magento3.jpg
+ magento3
+ jpg
+
+
+ VirtualProduct
+ virtual_sku
+ 99.99
+ None
+ 999
+ In Stock
+ IN STOCK
+ Catalog
+ virtual-product
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 5.00
+ None
+ Out of Stock
+ OUT OF STOCK
+ Catalog
+ virtual-product
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 120.00
+ Taxable Goods
+ 999
+ In Stock
+ IN STOCK
+ Catalog, Search
+ virtual-product
+ 45.00
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 99.99
+ None
+ Out of Stock
+ OUT OF STOCK
+ Catalog, Search
+ virtual-product
+ 45.00
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 145.00
+ Taxable Goods
+ 999
+ In Stock
+ IN STOCK
+ Catalog, Search
+ virtual-product
+ virtual
+
+
+ VirtualProduct
+ virtual_sku
+ 185.00
+ None
+ 999
+ Out of Stock
+ OUT OF STOCK
+ Catalog, Search
+ virtual-product
+ virtual
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 325.02
+ 89
+ In Stock
+ IN STOCK
+ 89.0000
+ Search
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 325.03
+ 25
+ Out of Stock
+ OUT OF STOCK
+ 125.0000
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 245.00
+ 200
+ In Stock
+ IN STOCK
+ 120.0000
+ Catalog, Search
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 325.01
+ 125
+ In Stock
+ IN STOCK
+ 25.0000
+ Catalog
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 300.00
+ 34
+ In Stock
+ IN STOCK
+ 1
+ This item has weight
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 1.99
+ Taxable Goods
+ 1000
+ 1
+ IN STOCK
+ 1
+ This item has weight
+ Catalog, Search
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 245.00
+ 343.00
+ 200
+ In Stock
+ IN STOCK
+ 120.0000
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 74.00
+ 87
+ In Stock
+ 333.0000
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 325.00
+ 123
+ In Stock
+ 129.0000
+ Not Visible Individually
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 9.99
+ simple
+ EavStock100
+
+
+ simple-product
+ SimpleProduct
+ 10000.00
+ 657
+ 50
+ UA
+ Ukraine
+ simple
+ 1
+ EavStock100
+
+
+ short_description
+
+
+
+ 4
+ 13
+ 0
+
+
+ AAA Product
+
+
+ BBB Product
+
+
+ Product "!@#$%^&*()+:;\|}{][?=~`
+ |}{][?=~`
+
+
+ ProductWith128Chars 1234567891123456789112345678911234567891123456789112345678911234567891123456789112345678 endnums
+
+
+ sku_simple_product_
+ simple
+ 4
+ 4
+ Simple Product
+ 560
+ simple-product-
+ 1
+ 25
+ 1
+ 1
+ 1
+ 2
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ sku_simple_product_
+ simple
+ 4
+ 4
+ SimpleProduct
+ 560
+ simple-product-
+ 1
+ 25
+ 1
+ 1
+ 1
+ 2
+ EavStockItem
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
index 6e532637fb6d..e9e9e4375236 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/ProductGridData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
index ea0bcafe56c4..71c8af318e9b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
@@ -12,4 +12,7 @@
10
100
+
+ 1
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinkData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinkData.xml
new file mode 100644
index 000000000000..000bb2095002
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinkData.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ related
+ simple
+ 1
+ Qty1000
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinksData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinksData.xml
new file mode 100644
index 000000000000..bd4f807880ab
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinksData.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ RelatedProductLink
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/SimpleProductOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/SimpleProductOptionData.xml
new file mode 100644
index 000000000000..157a4d410263
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/SimpleProductOptionData.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Test3 option
+ Drop-down
+ 1
+ 40 Percent
+ 40.00
+ Percent
+ sku_drop_down_row_1
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
index 39ecc2d440fc..7cba4c3c76fe 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 ce964e2d7150..0e51995ac72e 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/Data/TierPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml
new file mode 100644
index 000000000000..e5070340421a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+ $676.50
+ $615.00
+
+
+ secondStore
+ second_store
+
+
+ secondStoreView
+ second_store_view
+
+
+ All Websites [USD]
+ ALL GROUPS
+ 90.00
+ 2
+
+
+ All Websites [USD]
+ General
+ 80.00
+ 2
+
+
+ All Websites [USD]
+ ALL GROUPS
+ 15.00
+ 3
+ All Websites [USD]
+ ALL GROUPS
+ 24.00
+ 15
+
+
+ All Websites [USD]
+ ALL GROUPS
+ 500,000.00
+ 1
+
+
+ 90.00
+ fixed
+ 0
+ ALL GROUPS
+ 2
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/VirtualProductOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/VirtualProductOptionData.xml
new file mode 100644
index 000000000000..fe1d49e4daad
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/VirtualProductOptionData.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+ Test1 option
+ 1
+ Field
+ 120.03
+ Fixed
+ sku1_
+ 45
+
+
+ Test2 option
+ 1
+ Field
+ 120.03
+ Fixed
+ sku2_
+ 45
+
+
+ Test3 option
+ 1
+ Drop-down
+ Test3-1
+ 110.01
+ 9,900.90
+ Percent
+ sku3-1_
+ 0
+ Test3-2
+ 210.02
+ Fixed
+ sku3-2_
+ 1
+
+
+ Test4 option
+ 1
+ Drop-down
+ Test4-1
+ 10.01
+ Percent
+ sku4-1_
+ 0
+ Test4-2
+ 20.02
+ Fixed
+ sku4-2_
+ 1
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml
new file mode 100644
index 000000000000..18564ff101fd
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+ Catalog Product Link
+ Product Link Block Template
+
+
+ Recently Compared Products
+ Magento Luma
+ Recently Compared Products
+
+ - All Store Views
+
+ All Pages
+ Sidebar Additional
+
+
+ Recently Viewed Products
+ Magento Luma
+ Recently Viewed Products
+
+ - All Store Views
+
+ All Pages
+ Sidebar Additional
+
+
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 000000000000..9ef7b507812a
--- /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 000000000000..b1f2b43220b3
--- /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/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml
new file mode 100644
index 000000000000..1ee57c89b2b3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ string
+
+
+ string
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_recently_products-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_recently_products-meta.xml
new file mode 100644
index 000000000000..0fe4f154d5ef
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_recently_products-meta.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ integer
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_special_price-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_special_price-meta.xml
new file mode 100644
index 000000000000..354277ad056f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_special_price-meta.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ application/json
+
+
+ number
+ integer
+ string
+ string
+ string
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_tier_price-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_tier_price-meta.xml
new file mode 100644
index 000000000000..7aa7530b0fda
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_tier_price-meta.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ application/json
+
+
+ number
+ string
+ integer
+ string
+ string
+ 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/AdminCategoryPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
index 9349e188430f..f7d8abf8b2fe 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml
new file mode 100644
index 000000000000..dd5d5aef08a7
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
index fc776b49ba21..e4c4ece5ac6c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
@@ -15,7 +15,10 @@
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductDeletePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductDeletePage.xml
new file mode 100644
index 000000000000..1ce53a0ebd54
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductDeletePage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
index 5aaa78822af0..75e3210cad7d 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/AdminCatalogProductWidgetSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogProductWidgetSection.xml
new file mode 100644
index 000000000000..3261db1f63f2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCatalogProductWidgetSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
index 8b69a44993f1..977e63b9ec92 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
@@ -22,6 +22,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
index ed0325394d59..e3d224904671 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryContentSection.xml
@@ -15,5 +15,14 @@
+
+
+
+
+
+
+
+
+
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 000000000000..daa00eb0a27b
--- /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/AdminCategoryMainActionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml
index 009110a729bd..e8adede5b2de 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml
@@ -14,5 +14,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
index fee86ca1caa2..ea4f4bf53eb7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
@@ -10,5 +10,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
index 6b754dcc5d48..df79ec61ef73 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
@@ -14,5 +14,8 @@
+
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
index ef6fb99e88ee..fba28b3feaff 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
@@ -13,8 +13,9 @@
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
new file mode 100644
index 000000000000..2de7bf19fd37
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
index b83676c2e103..d24c501152b7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
@@ -17,15 +17,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml
index 99dfddcc776d..63bdcd52cdd2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditProductAttributesSection.xml
@@ -18,5 +18,8 @@
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSection.xml
new file mode 100644
index 000000000000..5329ad48c8f4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSelectProductPopupSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSelectProductPopupSection.xml
new file mode 100644
index 000000000000..0da67849f85c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSelectProductPopupSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAddAttributeModalSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAddAttributeModalSection.xml
new file mode 100644
index 000000000000..a3c98e43b451
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAddAttributeModalSection.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml
index 160948f8f1f2..5efd04eacb71 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml
@@ -10,11 +10,18 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
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 000000000000..5f1112eef362
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
index b906e2fa9084..3fad50adb771 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
@@ -14,5 +14,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributesSection.xml
new file mode 100644
index 000000000000..46a516b538f0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributesSection.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml
index 337cf0527dd4..755add18ec1c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCategoryCreationSection.xml
@@ -11,12 +11,10 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml
index a2ad155672a1..fafae5d53554 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductContentSection.xml
@@ -10,8 +10,10 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCrossSellModalSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCrossSellModalSection.xml
new file mode 100644
index 000000000000..803d72d7a7ec
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCrossSellModalSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
index 052195ec1aaa..352d219351fb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
@@ -13,13 +13,15 @@
-
+
-
-
-
+
+
+
+
+
@@ -28,19 +30,30 @@
+
+
-
+
-
-
-
-
-
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductDropdownOrderSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
new file mode 100644
index 000000000000..b58fb2316f91
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFiltersSection.xml
index ed08c84cdb6f..06ff54b2a399 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 afbaba41a9bb..1652546b0acb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
@@ -10,10 +10,13 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
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 000000000000..bc7c472df6ea
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
index 0a1804aa284d..3ef78a3fe8f4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
@@ -14,12 +14,14 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml
new file mode 100644
index 000000000000..a2a349ed6761
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
index cfd7e5ed3b8d..f515171e835d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
@@ -13,21 +13,32 @@
+
+
+
+
+
+
+
-
+
+
+
+
+
@@ -35,18 +46,30 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -104,24 +130,24 @@
-
+
-
-
+
+
-
-
-
-
-
+
+
+
+
+
-
+
@@ -142,21 +168,21 @@
-
+
-
+
-
+
-
+
@@ -164,8 +190,22 @@
+
+
+
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
index 611f12a39b51..939974248aab 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
@@ -28,8 +28,12 @@
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
index a7e20e22f1dd..07dd26381fe0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
@@ -8,6 +8,8 @@
+
+
@@ -15,6 +17,8 @@
+
+
@@ -28,6 +32,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 000000000000..7558b13d624b
--- /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/AdminProductImagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
index eca0cb6f02ea..89eb1ed678cc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
@@ -16,6 +16,7 @@
+
@@ -32,4 +33,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml
index adc3a753f06f..dbdc82026947 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductModalSlideGridSection.xml
@@ -10,5 +10,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
index e90d806805f7..ef596bed186e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
@@ -9,7 +9,10 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml
index c545fcd40883..53231a2a6863 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductSEOSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
index bf8812b3acef..53af1d5bd6eb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminUpdateAttributesSection.xml
@@ -38,4 +38,8 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/CatalogSubmenuSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/CatalogSubmenuSection.xml
new file mode 100644
index 000000000000..84a81c5204ac
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/CatalogSubmenuSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/NewProductPageSection.xml
similarity index 81%
rename from app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml
rename to app/code/Magento/Catalog/Test/Mftf/Section/NewProductPageSection.xml
index 42e451940c91..b98bd47b5413 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/NewProductPageSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/NewProductPageSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/ProductsPageSection.xml
similarity index 84%
rename from app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml
rename to app/code/Magento/Catalog/Test/Mftf/Section/ProductsPageSection.xml
index 267efdf3d0e5..ea37eb59b67f 100644
--- a/app/code/Magento/Braintree/Test/Mftf/Section/ProductsPageSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/ProductsPageSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml
new file mode 100644
index 000000000000..7ce795c78f25
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryBottomToolbarSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
index d484abf2069f..1cd64544d963 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
@@ -9,11 +9,15 @@
+
+
+
+
@@ -22,9 +26,13 @@
-
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
index 7ea74d791375..51b5a0242d97 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
@@ -16,6 +16,7 @@
+
@@ -28,7 +29,9 @@
-
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml
index 0599a42bfd7a..1b7bbd58eea9 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection.xml
@@ -13,4 +13,7 @@
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml
index 509ad2b8f849..52a377ad264c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml
@@ -9,6 +9,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
index dea1b2a5af75..c58479a7b73e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
@@ -10,5 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
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 000000000000..c6ea96715cf8
--- /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/Section/StorefrontNavigationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
index e8f35fc6787b..292b2d7008bc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
@@ -11,5 +11,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index b93a70559fc4..8393cee57996 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -13,13 +13,15 @@
-
+
+
+
-
+
@@ -28,14 +30,17 @@
-
+
+
+
+
+
-
@@ -49,7 +54,6 @@
-
@@ -66,8 +70,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
index 83c3ca534860..ea10e12fb73f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
@@ -9,6 +9,12 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml
index c87af1224ed3..8055ecfe00cd 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml
@@ -9,14 +9,19 @@
-
+
-
-
+
+
-
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductUpSellProductsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductUpSellProductsSection.xml
new file mode 100644
index 000000000000..f00abbe3c58c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductUpSellProductsSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml
new file mode 100644
index 000000000000..87aab45bd8cb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontWidgetsSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml
new file mode 100644
index 000000000000..4f66395bd0fb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml
new file mode 100644
index 000000000000..53bb12fda483
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml
index 88a39a9087bb..117f094ee060 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml
@@ -43,7 +43,9 @@
-
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml
index 5456cb02e74c..f657fbbdae60 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoSimpleProductTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml
index f48c352c5290..eab36bc90dc1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultVideoVirtualProductTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
index a280433e315f..03f3e93bb30e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
@@ -16,85 +16,77 @@
-
-
-
+
+
+
+
+
-
-
-
-
-
-
+
+
+
-
-
-
-
+
-
-
-
+
+
+
-
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
-
-
-
-
+
+
-
+
+
+
-
-
-
-
-
+
+
+
-
+
@@ -110,9 +102,5 @@
-
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
new file mode 100644
index 000000000000..e3f4d6cbdde0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml
index 72c0a4a51901..545e7c10379b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml
@@ -5,8 +5,7 @@
* See COPYING.txt for license details.
*/
-->
-
+
@@ -236,9 +235,10 @@
-
-
-
+
+
+
+
@@ -269,4 +269,56 @@
grabTextFromMiniCartSubtotalField2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogCategoriesNavigateMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogCategoriesNavigateMenuTest.xml
new file mode 100644
index 000000000000..a51df86d0327
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogCategoriesNavigateMenuTest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogProductsNavigateMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogProductsNavigateMenuTest.xml
new file mode 100644
index 000000000000..1d9400bf81e4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCatalogProductsNavigateMenuTest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml
new file mode 100644
index 000000000000..bcfab6ccfdf1
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml
new file mode 100644
index 000000000000..86978a4121a4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.00
+
+
+
+
+
+
+ 20.00
+
+
+
+
+
+
+ 30.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithOutOfStockChildProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithOutOfStockChildProductTest.xml
new file mode 100644
index 000000000000..8d41b276334a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithOutOfStockChildProductTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
new file mode 100644
index 000000000000..fd22142fcb09
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml
new file mode 100644
index 000000000000..b6c76d657721
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
new file mode 100644
index 000000000000..c9cd9acd9708
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsNotVisibleInCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsNotVisibleInCategoryTest.xml
new file mode 100644
index 000000000000..ee8b48a94b20
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsNotVisibleInCategoryTest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsVisibleInCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsVisibleInCategoryTest.xml
new file mode 100644
index 000000000000..e1cb45be22b4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsVisibleInCategoryTest.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml
new file mode 100644
index 000000000000..f40a62c164ec
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml
@@ -0,0 +1,176 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckSubCategoryIsNotVisibleInNavigationMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckSubCategoryIsNotVisibleInNavigationMenuTest.xml
new file mode 100644
index 000000000000..f5872ac3efca
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckSubCategoryIsNotVisibleInNavigationMenuTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000000..4d97dee56f05
--- /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/AdminCreateAndSwitchProductType.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
new file mode 100755
index 000000000000..4deca7350467
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml
new file mode 100644
index 000000000000..d9e410a9a300
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml
index bfb955791064..8806612c0f5d 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/AdminCreateCategoryWithAnchorFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml
new file mode 100644
index 000000000000..9115004ad958
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithCustomRootCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithCustomRootCategoryTest.xml
new file mode 100644
index 000000000000..e8c6da476a3d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithCustomRootCategoryTest.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml
new file mode 100644
index 000000000000..530bafaef24c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ['Home', {{FirstLevelSubCat.name}}, {{SecondLevelSubCat.name}}, {{ThirdLevelSubCat.name}}, {{FourthLevelSubCat.name}}, {{FifthLevelCat.name}} ]
+ breadcrumbs
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml
new file mode 100644
index 000000000000..96f945da138b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveCategoryTest.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml
new file mode 100644
index 000000000000..c983089163f7
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithInactiveIncludeInMenuTest.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml
new file mode 100644
index 000000000000..79eec02a828f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml
new file mode 100644
index 000000000000..1b6c9707b065
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
new file mode 100644
index 000000000000..a3f543e9cf32
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000000..525f81de6c48
--- /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/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
new file mode 100644
index 000000000000..1bc69be642a3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml
new file mode 100644
index 000000000000..37ec4e0d3252
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml
new file mode 100644
index 000000000000..575bb56912b2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml
new file mode 100644
index 000000000000..21b3dba7140c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml
new file mode 100644
index 000000000000..aa3dba85dfad
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml
new file mode 100644
index 000000000000..37417cd7fdb8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
new file mode 100644
index 000000000000..1f558568e924
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml
new file mode 100644
index 000000000000..282331924bca
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml
new file mode 100644
index 000000000000..5c798db29b97
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml
new file mode 100644
index 000000000000..d4d6496e018f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest.xml
index 95d74b965311..6658ad36d715 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/AdminCreateRootCategoryRequiredFieldsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml
new file mode 100644
index 000000000000..f98f9acc4696
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateRootCategoryRequiredFieldsTest.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml
new file mode 100644
index 000000000000..3487de656173
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithCountryOfManufactureAttributeSKUMaskTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml
new file mode 100644
index 000000000000..c3fe666c84fd
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml
new file mode 100644
index 000000000000..26ad7a46a73d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductOutOfStockWithTierPriceTest.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Buy {{tierPriceOnDefault.qty_0}} for ${{tierPriceOnDefault.price_0}} each and save 100%
+ firstTierPriceText
+
+
+
+ Buy {{tierPriceOnDefault.qty_1}} for ${{tierPriceOnDefault.price_1}} each and save 100%
+ secondTierPriceText
+
+
+
+
+
+ {{virtualProductOutOfStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{virtualProductOutOfStock.price}}
+ productPriceAmount
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
new file mode 100644
index 000000000000..17769c79677f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{virtualProductCustomImportOptions.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{virtualProductCustomImportOptions.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+ allCustomOptionLabels
+ [{{virtualProductCustomizableOption1.title}} + ${{virtualProductCustomizableOption1.option_0_price}}, {{virtualProductCustomizableOption2.title}} + ${{virtualProductCustomizableOption2.option_0_price}}, {{virtualProductCustomizableOption3.title}}, {{virtualProductCustomizableOption4.title}}]
+
+
+
+
+ grabFourthOptions
+ ['-- Please Select --', {{virtualProductCustomizableOption4.option_0_title}} +$900.90, {{virtualProductCustomizableOption4.option_1_title}} +$20.02]
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml
new file mode 100644
index 000000000000..78247f494359
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceForGeneralGroupTest.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ As low as ${{tierPriceOnGeneralGroup.price}}
+ tierPriceTextOnStorefrontPage
+
+
+
+
+
+ Buy {{tierPriceOnGeneralGroup.qty}} for ${{tierPriceOnGeneralGroup.price}} each and save 20%
+ tierPriceText
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml
new file mode 100644
index 000000000000..6ef2569945fa
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ As low as ${{tierPriceOnVirtualProduct.price}}
+ tierPriceTextOnStorefrontPage
+
+
+
+
+
+
+
+ Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 10%
+ tierPriceText
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml
new file mode 100644
index 000000000000..cb41b0292d33
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithoutManageStockTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${{virtualProductWithoutManageStock.special_price}}
+ specialPriceAmount
+
+
+
+
+
+ ${{virtualProductWithoutManageStock.price}}
+ oldPriceAmount
+
+
+
+
+
+ {{virtualProductWithoutManageStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml
new file mode 100644
index 000000000000..4d28ccbd44d2
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteAttributeSetTest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml
new file mode 100644
index 000000000000..0df9dd0b5754
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml
new file mode 100644
index 000000000000..3841c061c262
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml
new file mode 100644
index 000000000000..54b83e034fb1
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductAttributeTest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml
new file mode 100644
index 000000000000..7f6a1333b721
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryAssignedToStoreTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryAssignedToStoreTest.xml
new file mode 100644
index 000000000000..e4b269dff96b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryAssignedToStoreTest.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml
new file mode 100644
index 000000000000..e7ab14c77945
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootCategoryTest.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootSubCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootSubCategoryTest.xml
new file mode 100644
index 000000000000..6df571f403ac
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteRootSubCategoryTest.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml
new file mode 100644
index 000000000000..7c460a3dfc51
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml
new file mode 100644
index 000000000000..6de1a5cd359c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml
new file mode 100644
index 000000000000..c3cafb17c5ea
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml
new file mode 100644
index 000000000000..413d53d1c374
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml
index 7603400ba8dc..e914b8c96d03 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/AdminFilterByNameByStoreViewOnProductGridTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml
new file mode 100644
index 000000000000..f3ec225540c7
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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 000000000000..5c434ecabf80
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml
new file mode 100644
index 000000000000..b24ed7f9c9a8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml
new file mode 100644
index 000000000000..79ff7bcade77
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
new file mode 100644
index 000000000000..4d581bae700d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
index c0eebd1512d6..8a44c8093ca5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
@@ -58,7 +58,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
index 845c47c0e4c2..bee13bec370d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
@@ -52,7 +52,13 @@
-
+
+
+
+
+
+
+
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 000000000000..e9b54e3f1a3d
--- /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/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml
new file mode 100644
index 000000000000..247711295a55
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ['Home', $$createDefaultCategory.name$$,{{FirstLevelSubCat.name}}, {{SimpleSubCategory.name}}]
+ breadcrumbs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ['Home',{{SimpleSubCategory.name}}]
+ breadcrumbsAfterMove
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml
new file mode 100644
index 000000000000..ba6e6a43674c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml
new file mode 100644
index 000000000000..d17078d794b4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ['Home', $$createDefaultCategory.name$$,{{SimpleSubCategory.name}}]
+ breadcrumbs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ['Home',{{SimpleSubCategory.name}}]
+ breadcrumbsAfterMove
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml
new file mode 100644
index 000000000000..9831f73e0787
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ['Home', $$createDefaultCategory.name$$,{{FirstLevelSubCat.name}},{{SecondLevelSubCat.name}}]
+ breadcrumbs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ['Home',{{SecondLevelSubCat.name}}]
+ breadcrumbsAfterMove
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml
new file mode 100644
index 000000000000..bcd4ca853120
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml
@@ -0,0 +1,183 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml
new file mode 100644
index 000000000000..8149bc34087f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml
index 1bd218d18c27..876eedb9347c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoSimpleProductTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml
index e6d3978cad7b..8b3b38d0ece3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveDefaultVideoVirtualProductTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml
new file mode 100644
index 000000000000..8316f54c15a5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml
@@ -0,0 +1,131 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
new file mode 100644
index 000000000000..234a7c69913c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresAttributeSetNavigateMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresAttributeSetNavigateMenuTest.xml
new file mode 100644
index 000000000000..ed29c281b804
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresAttributeSetNavigateMenuTest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresProductNavigateMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresProductNavigateMenuTest.xml
new file mode 100644
index 000000000000..28a33c4f20c0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminStoresProductNavigateMenuTest.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml
new file mode 100644
index 000000000000..51af9d78dfd4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml
index dc6cf012840e..a33c7bb12879 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUnassignProductAttributeFromAttributeSetTest.xml
@@ -17,33 +17,25 @@
-
-
-
-
-
-
-
-
@@ -64,6 +56,8 @@
+
+
@@ -72,8 +66,7 @@
-
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml
new file mode 100644
index 000000000000..d8d462f850f8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml
new file mode 100644
index 000000000000..479249ca678d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndMakeInactiveTest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryNameWithStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryNameWithStoreViewTest.xml
new file mode 100644
index 000000000000..2cb4a6b6dd43
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryNameWithStoreViewTest.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml
new file mode 100644
index 000000000000..e7c4a8a093e1
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryUrlKeyWithStoreViewTest.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml
new file mode 100644
index 000000000000..3fea9c0eed7c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithInactiveIncludeInMenuTest.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml
new file mode 100644
index 000000000000..1cb01ac11cb8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml
new file mode 100644
index 000000000000..8872ea98eb50
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml
@@ -0,0 +1,103 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml
new file mode 100644
index 000000000000..552730337062
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml
new file mode 100644
index 000000000000..fcbc0cb20526
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml
new file mode 100644
index 000000000000..18e4ff9ee2c9
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml
new file mode 100644
index 000000000000..d5fc981b5b2e
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
new file mode 100644
index 000000000000..2c3aa5db7517
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductTierPrice300InStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductTierPrice300InStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml
new file mode 100644
index 000000000000..6e8f1ba6f12a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
new file mode 100644
index 000000000000..a042c4d60ae4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductEnabledFlat.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductEnabledFlat.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
new file mode 100644
index 000000000000..d08ef9c93999
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml
new file mode 100644
index 000000000000..3433a0911732
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
new file mode 100644
index 000000000000..a695982921cf
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPrice245InStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPrice245InStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
new file mode 100644
index 000000000000..ba52c6d2bc26
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPrice32501InStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPrice32501InStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
new file mode 100644
index 000000000000..cb5c24839e38
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPrice325InStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPrice325InStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
new file mode 100644
index 000000000000..318ab6555235
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPriceCustomOptions.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPriceCustomOptions.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+ grabFourthOptions
+ ['-- Please Select --', {{simpleProductCustomizableOption.option_0_title}} +$98.00]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
new file mode 100644
index 000000000000..54ed753b80a1
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPrice32503OutOfStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPrice32503OutOfStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml
new file mode 100644
index 000000000000..4dea6663e61b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithNoRedirectTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml
new file mode 100644
index 000000000000..ee1ed5f97edf
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateTopCategoryUrlWithRedirectTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml
new file mode 100644
index 000000000000..9bdc93e61e49
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ As low as ${{tierPriceOnVirtualProduct.price}}
+ tierPriceTextOnCategoryPage
+
+
+
+
+
+
+
+
+
+
+
+ Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 10%
+ tierPriceText
+
+
+ {{updateVirtualProductRegularPrice.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualProductRegularPrice.price}}
+ productPriceAmount
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
new file mode 100644
index 000000000000..34d85e7b4685
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
@@ -0,0 +1,249 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{updateVirtualProductRegularPriceInStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualProductRegularPriceInStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+ allCustomOptionLabels
+ [{{virtualProductCustomizableOption1.title}} + ${{virtualProductCustomizableOption1.option_0_price}}, {{virtualProductCustomizableOption2.title}} + ${{virtualProductCustomizableOption2.option_0_price}}, {{virtualProductCustomizableOption3.title}}, {{virtualProductCustomizableOption4.title}}]
+
+
+
+
+ grabFourthOptions
+ ['-- Please Select --', {{virtualProductCustomizableOption4.option_0_title}} +$12.01, {{virtualProductCustomizableOption4.option_1_title}} +$20.02]
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml
new file mode 100644
index 000000000000..a2a4f6586025
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryAndSearchTest.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{updateVirtualProductRegularPrice5OutOfStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualProductRegularPrice5OutOfStock.price}}
+ productPriceAmount
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml
new file mode 100644
index 000000000000..e64022b31161
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{updateVirtualProductRegularPrice5OutOfStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualProductRegularPrice5OutOfStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml
new file mode 100644
index 000000000000..aa3184994daf
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{updateVirtualProductRegularPrice99OutOfStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualProductRegularPrice99OutOfStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml
new file mode 100644
index 000000000000..9b6a56d6f81d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${{updateVirtualProductSpecialPrice.special_price}}
+ specialPriceAmount
+
+
+
+
+ ${{updateVirtualProductSpecialPrice.price}}
+ oldPriceAmount
+
+
+
+
+ {{updateVirtualProductSpecialPrice.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml
new file mode 100644
index 000000000000..920a0a494bae
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceOutOfStockVisibleInCategoryAndSearchTest.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{updateVirtualProductSpecialPriceOutOfStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualProductSpecialPriceOutOfStock.price}}
+ productPriceAmount
+
+
+
+
+ ${{updateVirtualProductSpecialPriceOutOfStock.special_price}}
+ specialPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml
new file mode 100644
index 000000000000..d4ec5e410d9f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryAndSearchTest.xml
@@ -0,0 +1,155 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{updateVirtualProductTierPriceInStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualProductTierPriceInStock.price}}
+ productPriceAmount
+
+
+
+ Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 38%
+ tierPriceText
+
+
+
+
+
+
+
+
+
+
+
+
+ As low as ${{tierPriceOnVirtualProduct.price}}
+ tierPriceTextOnStorefrontPage
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml
new file mode 100644
index 000000000000..717d710b4a28
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{updateVirtualProductWithTierPriceInStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualProductWithTierPriceInStock.price}}
+ productPriceAmount
+
+
+
+
+ Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 10%
+ tierPriceText
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml
new file mode 100644
index 000000000000..703a4e24cdca
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ selectedCategories
+ [$$categoryEntity.name$$]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{updateVirtualTierPriceOutOfStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{updateVirtualTierPriceOutOfStock.price}}
+ productPriceAmount
+
+
+
+
+ Buy {{tierPriceOnVirtualProduct.qty}} for ${{tierPriceOnVirtualProduct.price}} each and save 51%
+ tierPriceText
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml
new file mode 100644
index 000000000000..a81c26b6e6ea
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml
index 0eb8f5668751..84c3f81ef6db 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchVirtualProductTest.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml
new file mode 100644
index 000000000000..cee40241185b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml
@@ -0,0 +1,338 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice1
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice2
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice3
+
+
+
+
+
+
+
+
+
+
+
+ {{testDataTierPrice.goldenPrice2}}
+ $checkProductPrice4
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice5
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice7
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice8
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice9
+
+
+
+
+
+
+
+
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice10
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice11
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice12
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice13
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice14
+
+
+ {{testDataTierPrice.goldenPrice1}}
+ $checkProductPrice15
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml
new file mode 100644
index 000000000000..d895993217e3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml
@@ -0,0 +1,425 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml
new file mode 100644
index 000000000000..e79e4cea408f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml
new file mode 100644
index 000000000000..3dd55a9dfee9
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAvailableAfterEnablingSubCategoriesTest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml
index 569eb290bae3..386633f0e947 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuote.xml
@@ -17,10 +17,6 @@
-
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViews.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViews.xml
deleted file mode 100644
index 1df6ae654001..000000000000
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViews.xml
+++ /dev/null
@@ -1,292 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 100
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml
new file mode 100644
index 000000000000..fb95fc3f57bc
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductCustomOptionsDifferentStoreViewsTest.xml
@@ -0,0 +1,317 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 100
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml
deleted file mode 100644
index 6b444f1f6663..000000000000
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptions.xml
+++ /dev/null
@@ -1,182 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 17
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml
new file mode 100644
index 000000000000..a3bce2d4fe2f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 17
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
index 9d7c61623845..04cb813ec0ef 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
@@ -17,11 +17,6 @@
-
-
-
-
-
@@ -38,6 +33,8 @@
+
+
@@ -76,11 +73,20 @@
+
+
+
+
+
+
+
+
-
+
+
@@ -91,6 +97,7 @@
+
@@ -106,6 +113,6 @@
-
+
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 000000000000..0ed61b8636c4
--- /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/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml
new file mode 100644
index 000000000000..268e18d2b4ef
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 000000000000..4d7c97b26457
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/TieredPricingAndQuantityIncrementsWorkWithDecimalinventoryTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
index 26dd94b19de6..29ed3af4f01d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
@@ -32,16 +32,19 @@
-
-
+
+
+
+
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php
index 804eef25ebdd..249c32ff276c 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php
@@ -6,9 +6,13 @@
namespace Magento\Catalog\Test\Unit\Block\Adminhtml\Product\Helper\Form\Gallery;
use Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery\Content;
-use Magento\Framework\Filesystem;
+use Magento\Catalog\Model\Entity\Attribute;
+use Magento\Catalog\Model\Product;
use Magento\Framework\Phrase;
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class ContentTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -219,4 +223,146 @@ public function testGetImagesJsonWithException()
$this->assertSame(json_encode($imagesResult), $this->content->getImagesJson());
}
+
+ /**
+ * Test GetImageTypes() will return value for given attribute from data persistor.
+ *
+ * @return void
+ */
+ public function testGetImageTypesFromDataPersistor()
+ {
+ $attributeCode = 'thumbnail';
+ $value = 'testImageValue';
+ $scopeLabel = 'testScopeLabel';
+ $label = 'testLabel';
+ $name = 'testName';
+ $expectedTypes = [
+ $attributeCode => [
+ 'code' => $attributeCode,
+ 'value' => $value,
+ 'label' => $label,
+ 'name' => $name,
+ ],
+ ];
+ $product = $this->getMockBuilder(Product::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $product->expects($this->once())
+ ->method('getData')
+ ->with($this->identicalTo($attributeCode))
+ ->willReturn(null);
+ $mediaAttribute = $this->getMediaAttribute($label, $attributeCode);
+ $product->expects($this->once())
+ ->method('getMediaAttributes')
+ ->willReturn([$mediaAttribute]);
+ $this->galleryMock->expects($this->exactly(2))
+ ->method('getDataObject')
+ ->willReturn($product);
+ $this->galleryMock->expects($this->once())
+ ->method('getImageValue')
+ ->with($this->identicalTo($attributeCode))
+ ->willReturn($value);
+ $this->galleryMock->expects($this->once())
+ ->method('getScopeLabel')
+ ->with($this->identicalTo($mediaAttribute))
+ ->willReturn($scopeLabel);
+ $this->galleryMock->expects($this->once())
+ ->method('getAttributeFieldName')
+ ->with($this->identicalTo($mediaAttribute))
+ ->willReturn($name);
+ $this->getImageTypesAssertions($attributeCode, $scopeLabel, $expectedTypes);
+ }
+
+ /**
+ * Test GetImageTypes() will return value for given attribute from product.
+ *
+ * @return void
+ */
+ public function testGetImageTypesFromProduct()
+ {
+ $attributeCode = 'thumbnail';
+ $value = 'testImageValue';
+ $scopeLabel = 'testScopeLabel';
+ $label = 'testLabel';
+ $name = 'testName';
+ $expectedTypes = [
+ $attributeCode => [
+ 'code' => $attributeCode,
+ 'value' => $value,
+ 'label' => $label,
+ 'name' => $name,
+ ],
+ ];
+ $product = $this->getMockBuilder(Product::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $product->expects($this->once())
+ ->method('getData')
+ ->with($this->identicalTo($attributeCode))
+ ->willReturn($value);
+ $mediaAttribute = $this->getMediaAttribute($label, $attributeCode);
+ $product->expects($this->once())
+ ->method('getMediaAttributes')
+ ->willReturn([$mediaAttribute]);
+ $this->galleryMock->expects($this->exactly(2))
+ ->method('getDataObject')
+ ->willReturn($product);
+ $this->galleryMock->expects($this->never())
+ ->method('getImageValue');
+ $this->galleryMock->expects($this->once())
+ ->method('getScopeLabel')
+ ->with($this->identicalTo($mediaAttribute))
+ ->willReturn($scopeLabel);
+ $this->galleryMock->expects($this->once())
+ ->method('getAttributeFieldName')
+ ->with($this->identicalTo($mediaAttribute))
+ ->willReturn($name);
+ $this->getImageTypesAssertions($attributeCode, $scopeLabel, $expectedTypes);
+ }
+
+ /**
+ * Perform assertions.
+ *
+ * @param string $attributeCode
+ * @param string $scopeLabel
+ * @param array $expectedTypes
+ * @return void
+ */
+ private function getImageTypesAssertions(string $attributeCode, string $scopeLabel, array $expectedTypes)
+ {
+ $this->content->setElement($this->galleryMock);
+ $result = $this->content->getImageTypes();
+ $scope = $result[$attributeCode]['scope'];
+ $this->assertSame($scopeLabel, $scope->getText());
+ unset($result[$attributeCode]['scope']);
+ $this->assertSame($expectedTypes, $result);
+ }
+
+ /**
+ * Get media attribute mock.
+ *
+ * @param string $label
+ * @param string $attributeCode
+ * @return \PHPUnit_Framework_MockObject_MockObject
+ */
+ private function getMediaAttribute(string $label, string $attributeCode)
+ {
+ $frontend = $this->getMockBuilder(Product\Attribute\Frontend\Image::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $frontend->expects($this->once())
+ ->method('getLabel')
+ ->willReturn($label);
+ $mediaAttribute = $this->getMockBuilder(Attribute::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $mediaAttribute->expects($this->any())
+ ->method('getAttributeCode')
+ ->willReturn($attributeCode);
+ $mediaAttribute->expects($this->once())
+ ->method('getFrontend')
+ ->willReturn($frontend);
+
+ return $mediaAttribute;
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/GalleryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/GalleryTest.php
index 06e2368f3080..1e04680676eb 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/GalleryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Product/Helper/Form/GalleryTest.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Test\Unit\Block\Adminhtml\Product\Helper\Form;
+use Magento\Framework\App\Request\DataPersistorInterface;
+
class GalleryTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -32,18 +34,27 @@ class GalleryTest extends \PHPUnit\Framework\TestCase
*/
protected $objectManager;
+ /**
+ * @var DataPersistorInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $dataPersistorMock;
+
public function setUp()
{
$this->registryMock = $this->createMock(\Magento\Framework\Registry::class);
$this->productMock = $this->createPartialMock(\Magento\Catalog\Model\Product::class, ['getData']);
$this->formMock = $this->createMock(\Magento\Framework\Data\Form::class);
-
+ $this->dataPersistorMock = $this->getMockBuilder(DataPersistorInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['get'])
+ ->getMockForAbstractClass();
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->gallery = $this->objectManager->getObject(
\Magento\Catalog\Block\Adminhtml\Product\Helper\Form\Gallery::class,
[
'registry' => $this->registryMock,
- 'form' => $this->formMock
+ 'form' => $this->formMock,
+ 'dataPersistor' => $this->dataPersistorMock
]
);
}
@@ -70,6 +81,68 @@ public function testGetImages()
$this->assertSame($mediaGallery, $this->gallery->getImages());
}
+ /**
+ * Test getImages() will try get data from data persistor, if it's absent in registry.
+ *
+ * @return void
+ */
+ public function testGetImagesWithDataPersistor()
+ {
+ $product = [
+ 'product' => [
+ 'media_gallery' => [
+ 'images' => [
+ [
+ 'value_id' => '1',
+ 'file' => 'image_1.jpg',
+ 'media_type' => 'image',
+ ],
+ [
+ 'value_id' => '2',
+ 'file' => 'image_2.jpg',
+ 'media_type' => 'image',
+ ],
+ ],
+ ],
+ ],
+ ];
+ $this->registryMock->expects($this->once())->method('registry')->willReturn($this->productMock);
+ $this->productMock->expects($this->once())->method('getData')->willReturn(null);
+ $this->dataPersistorMock->expects($this->once())
+ ->method('get')
+ ->with($this->identicalTo('catalog_product'))
+ ->willReturn($product);
+
+ $this->assertSame($product['product']['media_gallery'], $this->gallery->getImages());
+ }
+
+ /**
+ * Test get image value from data persistor in case it's absent in product from registry.
+ *
+ * @return void
+ */
+ public function testGetImageValue()
+ {
+ $product = [
+ 'product' => [
+ 'media_gallery' => [
+ 'images' => [
+ 'value_id' => '1',
+ 'file' => 'image_1.jpg',
+ 'media_type' => 'image',
+ ],
+ ],
+ 'small' => 'testSmallImage',
+ 'thumbnail' => 'testThumbnail'
+ ]
+ ];
+ $this->dataPersistorMock->expects($this->once())
+ ->method('get')
+ ->with($this->identicalTo('catalog_product'))
+ ->willReturn($product);
+ $this->assertSame($product['product']['small'], $this->gallery->getImageValue('small'));
+ }
+
public function testGetDataObject()
{
$this->registryMock->expects($this->once())->method('registry')->willReturn($this->productMock);
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/NotifyStockTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/NotifyStockTest.php
index 1dd866f1fe2c..da35d845468d 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/NotifyStockTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Adminhtml/Rss/NotifyStockTest.php
@@ -96,7 +96,12 @@ public function testGetRssData()
$this->urlBuilder->expects($this->once())->method('getUrl')
->with('catalog/product/edit', ['id' => 1, '_secure' => true, '_nosecret' => true])
->will($this->returnValue('http://magento.com/catalog/product/edit/id/1'));
- $this->assertEquals($this->rssFeed, $this->block->getRssData());
+
+ $data = $this->block->getRssData();
+ $this->assertTrue(is_string($data['title']));
+ $this->assertTrue(is_string($data['description']));
+ $this->assertTrue(is_string($data['entries'][0]['description']));
+ $this->assertEquals($this->rssFeed, $data);
}
public function testGetCacheLifetime()
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 8a42865a3fe4..95b06e40602b 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 ac963326dbfa..884f4c543c8b 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/Block/Product/View/GalleryOptionsTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php
new file mode 100644
index 000000000000..7ed8b13fce75
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php
@@ -0,0 +1,223 @@
+escaper = $objectManager->getObject(Escaper::class);
+ $this->configView = $this->createMock(View::class);
+
+ $this->viewConfig = $this->createConfiguredMock(
+ Config::class,
+ [
+ 'getViewConfig' => $this->configView
+ ]
+ );
+
+ $this->context = $this->createConfiguredMock(
+ Context::class,
+ [
+ 'getEscaper' => $this->escaper,
+ 'getViewConfig' => $this->viewConfig
+ ]
+ );
+
+ $this->gallery = $this->createMock(Gallery::class);
+
+ $this->jsonSerializer = $objectManager->getObject(
+ Json::class
+ );
+
+ $this->model = $objectManager->getObject(GalleryOptions::class, [
+ 'context' => $this->context,
+ 'jsonSerializer' => $this->jsonSerializer,
+ 'gallery' => $this->gallery
+ ]);
+ }
+
+ public function testGetOptionsJson()
+ {
+ $configMap = [
+ ['Magento_Catalog', 'gallery/nav', 'thumbs'],
+ ['Magento_Catalog', 'gallery/loop', false],
+ ['Magento_Catalog', 'gallery/keyboard', true],
+ ['Magento_Catalog', 'gallery/arrows', true],
+ ['Magento_Catalog', 'gallery/caption', false],
+ ['Magento_Catalog', 'gallery/allowfullscreen', true],
+ ['Magento_Catalog', 'gallery/navdir', 'horizontal'],
+ ['Magento_Catalog', 'gallery/navarrows', true],
+ ['Magento_Catalog', 'gallery/navtype', 'slides'],
+ ['Magento_Catalog', 'gallery/thumbmargin', '5'],
+ ['Magento_Catalog', 'gallery/transition/effect', 'slide'],
+ ['Magento_Catalog', 'gallery/transition/duration', '500'],
+ ];
+
+ $imageAttributesMap = [
+ ['product_page_image_medium','height',null, 100],
+ ['product_page_image_medium','width',null, 200],
+ ['product_page_image_small','height',null, 300],
+ ['product_page_image_small','width',null, 400]
+ ];
+
+ $this->configView->expects($this->any())
+ ->method('getVarValue')
+ ->will($this->returnValueMap($configMap));
+ $this->gallery->expects($this->any())
+ ->method('getImageAttribute')
+ ->will($this->returnValueMap($imageAttributesMap));
+
+ $json = $this->model->getOptionsJson();
+
+ $decodedJson = $this->jsonSerializer->unserialize($json);
+
+ $this->assertSame('thumbs', $decodedJson['nav']);
+ $this->assertSame(false, $decodedJson['loop']);
+ $this->assertSame(true, $decodedJson['keyboard']);
+ $this->assertSame(true, $decodedJson['arrows']);
+ $this->assertSame(false, $decodedJson['showCaption']);
+ $this->assertSame(true, $decodedJson['allowfullscreen']);
+ $this->assertSame('horizontal', $decodedJson['navdir']);
+ $this->assertSame(true, $decodedJson['navarrows']);
+ $this->assertSame('slides', $decodedJson['navtype']);
+ $this->assertSame(5, $decodedJson['thumbmargin']);
+ $this->assertSame('slide', $decodedJson['transition']);
+ $this->assertSame(500, $decodedJson['transitionduration']);
+ $this->assertSame(100, $decodedJson['height']);
+ $this->assertSame(200, $decodedJson['width']);
+ $this->assertSame(300, $decodedJson['thumbheight']);
+ $this->assertSame(400, $decodedJson['thumbwidth']);
+ }
+
+ public function testGetFSOptionsJson()
+ {
+ $configMap = [
+ ['Magento_Catalog', 'gallery/fullscreen/nav', false],
+ ['Magento_Catalog', 'gallery/fullscreen/loop', true],
+ ['Magento_Catalog', 'gallery/fullscreen/keyboard', true],
+ ['Magento_Catalog', 'gallery/fullscreen/arrows', false],
+ ['Magento_Catalog', 'gallery/fullscreen/caption', true],
+ ['Magento_Catalog', 'gallery/fullscreen/navdir', 'vertical'],
+ ['Magento_Catalog', 'gallery/fullscreen/navarrows', false],
+ ['Magento_Catalog', 'gallery/fullscreen/navtype', 'thumbs'],
+ ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', '10'],
+ ['Magento_Catalog', 'gallery/fullscreen/transition/effect', 'dissolve'],
+ ['Magento_Catalog', 'gallery/fullscreen/transition/duration', '300']
+ ];
+
+ $this->configView->expects($this->any())
+ ->method('getVarValue')
+ ->will($this->returnValueMap($configMap));
+
+ $json = $this->model->getFSOptionsJson();
+
+ $decodedJson = $this->jsonSerializer->unserialize($json);
+
+ //Note, this tests the special case for nav variable set to false. It
+ //Should not be converted to boolean.
+ $this->assertSame('false', $decodedJson['nav']);
+ $this->assertSame(true, $decodedJson['loop']);
+ $this->assertSame(false, $decodedJson['arrows']);
+ $this->assertSame(true, $decodedJson['keyboard']);
+ $this->assertSame(true, $decodedJson['showCaption']);
+ $this->assertSame('vertical', $decodedJson['navdir']);
+ $this->assertSame(false, $decodedJson['navarrows']);
+ $this->assertSame(10, $decodedJson['thumbmargin']);
+ $this->assertSame('thumbs', $decodedJson['navtype']);
+ $this->assertSame('dissolve', $decodedJson['transition']);
+ $this->assertSame(300, $decodedJson['transitionduration']);
+ }
+
+ public function testGetOptionsJsonOptionals()
+ {
+ $configMap = [
+ ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', false],
+ ['Magento_Catalog', 'gallery/fullscreen/transition/duration', false]
+ ];
+
+ $this->configView->expects($this->any())
+ ->method('getVarValue')
+ ->will($this->returnValueMap($configMap));
+
+ $json = $this->model->getOptionsJson();
+
+ $decodedJson = $this->jsonSerializer->unserialize($json);
+
+ $this->assertArrayNotHasKey('thumbmargin', $decodedJson);
+ $this->assertArrayNotHasKey('transitionduration', $decodedJson);
+ }
+
+ public function testGetFSOptionsJsonOptionals()
+ {
+ $configMap = [
+ ['Magento_Catalog', 'gallery/fullscreen/keyboard', false],
+ ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', false],
+ ['Magento_Catalog', 'gallery/fullscreen/transition/duration', false]
+ ];
+
+ $this->configView->expects($this->any())
+ ->method('getVarValue')
+ ->will($this->returnValueMap($configMap));
+
+ $json = $this->model->getFSOptionsJson();
+
+ $decodedJson = $this->jsonSerializer->unserialize($json);
+
+ $this->assertArrayNotHasKey('thumbmargin', $decodedJson);
+ $this->assertArrayNotHasKey('keyboard', $decodedJson);
+ $this->assertArrayNotHasKey('transitionduration', $decodedJson);
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryTest.php
index c9b7dc50beb9..a81d8b1c9fc3 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryTest.php
@@ -91,6 +91,94 @@ protected function setUp()
]);
}
+ public function testGetGalleryImagesJsonWithLabel()
+ {
+ $this->prepareGetGalleryImagesJsonMocks();
+ $json = $this->model->getGalleryImagesJson();
+ $decodedJson = json_decode($json, true);
+ $this->assertEquals('product_page_image_small_url', $decodedJson[0]['thumb']);
+ $this->assertEquals('product_page_image_medium_url', $decodedJson[0]['img']);
+ $this->assertEquals('product_page_image_large_url', $decodedJson[0]['full']);
+ $this->assertEquals('test_label', $decodedJson[0]['caption']);
+ $this->assertEquals('2', $decodedJson[0]['position']);
+ $this->assertEquals(false, $decodedJson[0]['isMain']);
+ $this->assertEquals('test_media_type', $decodedJson[0]['type']);
+ $this->assertEquals('test_video_url', $decodedJson[0]['videoUrl']);
+ }
+
+ public function testGetGalleryImagesJsonWithoutLabel()
+ {
+ $this->prepareGetGalleryImagesJsonMocks(false);
+ $json = $this->model->getGalleryImagesJson();
+ $decodedJson = json_decode($json, true);
+ $this->assertEquals('test_product_name', $decodedJson[0]['caption']);
+ }
+
+ private function prepareGetGalleryImagesJsonMocks($hasLabel = true)
+ {
+ $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $productTypeMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Type\AbstractType::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $productTypeMock->expects($this->any())
+ ->method('getStoreFilter')
+ ->with($productMock)
+ ->willReturn($storeMock);
+
+ $productMock->expects($this->any())
+ ->method('getTypeInstance')
+ ->willReturn($productTypeMock);
+ $productMock->expects($this->any())
+ ->method('getMediaGalleryImages')
+ ->willReturn($this->getImagesCollectionWithPopulatedDataObject($hasLabel));
+ $productMock->expects($this->any())
+ ->method('getName')
+ ->willReturn('test_product_name');
+
+ $this->registry->expects($this->any())
+ ->method('registry')
+ ->with('product')
+ ->willReturn($productMock);
+
+ $this->imageHelper = $this->getMockBuilder(\Magento\Catalog\Helper\Image::class)
+ ->setMethods(['init', 'setImageFile', 'getUrl'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->imageHelper->expects($this->any())
+ ->method('init')
+ ->willReturnMap([
+ [$productMock, 'product_page_image_small', [], $this->imageHelper],
+ [$productMock, 'product_page_image_medium_no_frame', [], $this->imageHelper],
+ [$productMock, 'product_page_image_large_no_frame', [], $this->imageHelper],
+ ])
+ ->willReturnSelf();
+ $this->imageHelper->expects($this->any())
+ ->method('setImageFile')
+ ->with('test_file')
+ ->willReturnSelf();
+ $this->urlBuilder->expects($this->at(0))
+ ->method('getUrl')
+ ->willReturn('product_page_image_small_url');
+ $this->urlBuilder->expects($this->at(1))
+ ->method('getUrl')
+ ->willReturn('product_page_image_medium_url');
+ $this->urlBuilder->expects($this->at(2))
+ ->method('getUrl')
+ ->willReturn('product_page_image_large_url');
+
+ $this->galleryImagesConfigMock->expects($this->exactly(2))
+ ->method('getItems')
+ ->willReturn($this->getGalleryImagesConfigItems());
+ }
+
public function testGetGalleryImages()
{
$productMock = $this->createMock(Product::class);
@@ -163,4 +251,30 @@ private function getGalleryImagesConfigItems()
])
];
}
+
+ /**
+ * @return \Magento\Framework\Data\Collection
+ */
+ private function getImagesCollectionWithPopulatedDataObject($hasLabel)
+ {
+ $collectionMock = $this->getMockBuilder(\Magento\Framework\Data\Collection::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $items = [
+ new \Magento\Framework\DataObject([
+ 'file' => 'test_file',
+ 'label' => ($hasLabel ? 'test_label' : ''),
+ 'position' => '2',
+ 'media_type' => 'external-test_media_type',
+ "video_url" => 'test_video_url'
+ ]),
+ ];
+
+ $collectionMock->expects($this->any())
+ ->method('getIterator')
+ ->willReturn(new \ArrayIterator($items));
+
+ return $collectionMock;
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/RefreshPathTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/RefreshPathTest.php
index 45de62e218cf..adf00333721b 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/RefreshPathTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/RefreshPathTest.php
@@ -66,8 +66,8 @@ private function setObjectProperty($object, string $propertyName, $value) : void
*/
public function testExecute() : void
{
- $value = ['id' => 3, 'path' => '1/2/3', 'parentId' => 2];
- $result = '{"id":3,"path":"1/2/3","parentId":"2"}';
+ $value = ['id' => 3, 'path' => '1/2/3', 'parentId' => 2, 'level' => 2];
+ $result = '{"id":3,"path":"1/2/3","parentId":"2","level":"2"}';
$requestMock = $this->getMockForAbstractClass(\Magento\Framework\App\RequestInterface::class);
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php
deleted file mode 100644
index de44af7f58af..000000000000
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php
+++ /dev/null
@@ -1,258 +0,0 @@
-attributeHelper = $this->createPartialMock(
- \Magento\Catalog\Helper\Product\Edit\Action\Attribute::class,
- ['getProductIds', 'getSelectedStoreId', 'getStoreWebsiteId']
- );
-
- $this->dataObjectHelperMock = $this->getMockBuilder(\Magento\Framework\Api\DataObjectHelper::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->stockIndexerProcessor = $this->createPartialMock(
- \Magento\CatalogInventory\Model\Indexer\Stock\Processor::class,
- ['reindexList']
- );
-
- $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->resultRedirectFactory = $this->getMockBuilder(\Magento\Backend\Model\View\Result\RedirectFactory::class)
- ->disableOriginalConstructor()
- ->setMethods(['create'])
- ->getMock();
- $this->resultRedirectFactory->expects($this->atLeastOnce())
- ->method('create')
- ->willReturn($resultRedirect);
-
- $this->prepareContext();
-
- $this->object = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject(
- \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Save::class,
- [
- 'context' => $this->context,
- 'attributeHelper' => $this->attributeHelper,
- 'stockIndexerProcessor' => $this->stockIndexerProcessor,
- 'dataObjectHelper' => $this->dataObjectHelperMock,
- ]
- );
- }
-
- /**
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- protected function prepareContext()
- {
- $this->stockItemRepository = $this->getMockBuilder(
- \Magento\CatalogInventory\Api\StockItemRepositoryInterface::class
- )->disableOriginalConstructor()->getMock();
-
- $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class)
- ->disableOriginalConstructor()->getMock();
- $this->response = $this->createMock(\Magento\Framework\App\Response\Http::class);
- $this->objectManager = $this->createMock(\Magento\Framework\ObjectManagerInterface::class);
- $this->eventManager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
- $this->url = $this->createMock(\Magento\Framework\UrlInterface::class);
- $this->redirect = $this->createMock(\Magento\Framework\App\Response\RedirectInterface::class);
- $this->actionFlag = $this->createMock(\Magento\Framework\App\ActionFlag::class);
- $this->view = $this->createMock(\Magento\Framework\App\ViewInterface::class);
- $this->messageManager = $this->createMock(\Magento\Framework\Message\ManagerInterface::class);
- $this->session = $this->createMock(\Magento\Backend\Model\Session::class);
- $this->authorization = $this->createMock(\Magento\Framework\AuthorizationInterface::class);
- $this->auth = $this->createMock(\Magento\Backend\Model\Auth::class);
- $this->helper = $this->createMock(\Magento\Backend\Helper\Data::class);
- $this->backendUrl = $this->createMock(\Magento\Backend\Model\UrlInterface::class);
- $this->formKeyValidator = $this->createMock(\Magento\Framework\Data\Form\FormKey\Validator::class);
- $this->localeResolver = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class);
-
- $this->context = $this->context = $this->createPartialMock(\Magento\Backend\App\Action\Context::class, [
- 'getRequest',
- 'getResponse',
- 'getObjectManager',
- 'getEventManager',
- 'getUrl',
- 'getRedirect',
- 'getActionFlag',
- 'getView',
- 'getMessageManager',
- 'getSession',
- 'getAuthorization',
- 'getAuth',
- 'getHelper',
- 'getBackendUrl',
- 'getFormKeyValidator',
- 'getLocaleResolver',
- 'getResultRedirectFactory'
- ]);
- $this->context->expects($this->any())->method('getRequest')->willReturn($this->request);
- $this->context->expects($this->any())->method('getResponse')->willReturn($this->response);
- $this->context->expects($this->any())->method('getObjectManager')->willReturn($this->objectManager);
- $this->context->expects($this->any())->method('getEventManager')->willReturn($this->eventManager);
- $this->context->expects($this->any())->method('getUrl')->willReturn($this->url);
- $this->context->expects($this->any())->method('getRedirect')->willReturn($this->redirect);
- $this->context->expects($this->any())->method('getActionFlag')->willReturn($this->actionFlag);
- $this->context->expects($this->any())->method('getView')->willReturn($this->view);
- $this->context->expects($this->any())->method('getMessageManager')->willReturn($this->messageManager);
- $this->context->expects($this->any())->method('getSession')->willReturn($this->session);
- $this->context->expects($this->any())->method('getAuthorization')->willReturn($this->authorization);
- $this->context->expects($this->any())->method('getAuth')->willReturn($this->auth);
- $this->context->expects($this->any())->method('getHelper')->willReturn($this->helper);
- $this->context->expects($this->any())->method('getBackendUrl')->willReturn($this->backendUrl);
- $this->context->expects($this->any())->method('getFormKeyValidator')->willReturn($this->formKeyValidator);
- $this->context->expects($this->any())->method('getLocaleResolver')->willReturn($this->localeResolver);
- $this->context->expects($this->any())
- ->method('getResultRedirectFactory')
- ->willReturn($this->resultRedirectFactory);
-
- $this->product = $this->createPartialMock(
- \Magento\Catalog\Model\Product::class,
- ['isProductsHasSku', '__wakeup']
- );
-
- $this->stockItemService = $this->getMockBuilder(\Magento\CatalogInventory\Api\StockRegistryInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['getStockItem', 'saveStockItem'])
- ->getMockForAbstractClass();
- $this->stockItem = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class)
- ->setMethods(['getId', 'getProductId'])
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
-
- $this->stockConfig = $this->getMockBuilder(\Magento\CatalogInventory\Api\StockConfigurationInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
-
- $this->objectManager->expects($this->any())->method('create')->will($this->returnValueMap([
- [\Magento\Catalog\Model\Product::class, [], $this->product],
- [\Magento\CatalogInventory\Api\StockRegistryInterface::class, [], $this->stockItemService],
- [\Magento\CatalogInventory\Api\StockItemRepositoryInterface::class, [], $this->stockItemRepository],
- ]));
-
- $this->objectManager->expects($this->any())->method('get')->will($this->returnValueMap([
- [\Magento\CatalogInventory\Api\StockConfigurationInterface::class, $this->stockConfig],
- ]));
- }
-
- public function testExecuteThatProductIdsAreObtainedFromAttributeHelper()
- {
- $this->attributeHelper->expects($this->any())->method('getProductIds')->will($this->returnValue([5]));
- $this->attributeHelper->expects($this->any())->method('getSelectedStoreId')->will($this->returnValue([1]));
- $this->attributeHelper->expects($this->any())->method('getStoreWebsiteId')->will($this->returnValue(1));
- $this->stockConfig->expects($this->any())->method('getConfigItemOptions')->will($this->returnValue([]));
- $this->dataObjectHelperMock->expects($this->any())
- ->method('populateWithArray')
- ->with($this->stockItem, $this->anything(), \Magento\CatalogInventory\Api\Data\StockItemInterface::class)
- ->willReturnSelf();
- $this->product->expects($this->any())->method('isProductsHasSku')->with([5])->will($this->returnValue(true));
- $this->stockItemService->expects($this->any())->method('getStockItem')->with(5, 1)
- ->will($this->returnValue($this->stockItem));
- $this->stockIndexerProcessor->expects($this->any())->method('reindexList')->with([5]);
-
- $this->request->expects($this->any())->method('getParam')->will($this->returnValueMap([
- ['inventory', [], [7]],
- ]));
-
- $this->messageManager->expects($this->never())->method('addErrorMessage');
- $this->messageManager->expects($this->never())->method('addExceptionMessage');
-
- $this->object->execute();
- }
-}
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 f493cbc88f18..30d3503e4640 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,10 @@
*/
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\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator;
+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 +16,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 +85,21 @@ class SaveTest extends AttributeTest
*/
protected $inputTypeValidatorMock;
+ /**
+ * @var FormData|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $formDataSerializerMock;
+
+ /**
+ * @var ProductAttributeInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $productAttributeMock;
+
+ /**
+ * @var AttributeCodeValidator|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $attributeCodeValidatorMock;
+
protected function setUp()
{
parent::setUp();
@@ -108,6 +129,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 +141,15 @@ protected function setUp()
$this->inputTypeValidatorMock = $this->getMockBuilder(InputTypeValidator::class)
->disableOriginalConstructor()
->getMock();
+ $this->formDataSerializerMock = $this->getMockBuilder(FormData::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->attributeCodeValidatorMock = $this->getMockBuilder(AttributeCodeValidator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class)
+ ->setMethods(['getId', 'get'])
+ ->getMockForAbstractClass();
$this->buildFactoryMock->expects($this->any())
->method('create')
@@ -126,6 +157,9 @@ protected function setUp()
$this->validatorFactoryMock->expects($this->any())
->method('create')
->willReturn($this->inputTypeValidatorMock);
+ $this->attributeFactoryMock
+ ->method('create')
+ ->willReturn($this->productAttributeMock);
}
/**
@@ -145,11 +179,24 @@ protected function getModel()
'validatorFactory' => $this->validatorFactoryMock,
'groupCollectionFactory' => $this->groupCollectionFactoryMock,
'layoutFactory' => $this->layoutFactoryMock,
+ 'formDataSerializer' => $this->formDataSerializerMock,
+ 'attributeCodeValidator' => $this->attributeCodeValidatorMock
]);
}
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 +217,27 @@ 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->attributeCodeValidatorMock
+ ->method('isValid')
+ ->with('test_code')
+ ->willReturn(true);
$this->requestMock->expects($this->once())
->method('getPostValue')
->willReturn($data);
@@ -203,4 +271,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 9c747393cc72..742148b1bf7f 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,8 @@
namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Attribute;
use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate;
+use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator;
+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 +63,16 @@ class ValidateTest extends AttributeTest
*/
protected $layoutMock;
+ /**
+ * @var FormData|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $formDataSerializerMock;
+
+ /**
+ * @var AttributeCodeValidator|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $attributeCodeValidatorMock;
+
protected function setUp()
{
parent::setUp();
@@ -86,6 +98,12 @@ protected function setUp()
->getMock();
$this->layoutMock = $this->getMockBuilder(LayoutInterface::class)
->getMockForAbstractClass();
+ $this->formDataSerializerMock = $this->getMockBuilder(FormData::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->attributeCodeValidatorMock = $this->getMockBuilder(AttributeCodeValidator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->contextMock->expects($this->any())
->method('getObjectManager')
@@ -100,25 +118,29 @@ 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,
+ 'attributeCodeValidator' => $this->attributeCodeValidatorMock,
]
);
}
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')
@@ -129,6 +151,12 @@ public function testExecute()
$this->attributeMock->expects($this->once())
->method('loadByCode')
->willReturnSelf();
+
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('isValid')
+ ->with('test_attribute_code')
+ ->willReturn(true);
+
$this->requestMock->expects($this->once())
->method('has')
->with('new_attribute_set_name')
@@ -160,6 +188,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 +196,21 @@ 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->attributeCodeValidatorMock->expects($this->once())
+ ->method('isValid')
+ ->with('test_attribute_code')
+ ->willReturn(true);
+
$this->objectManagerMock->expects($this->once())
->method('create')
->willReturn($this->attributeMock);
@@ -203,67 +243,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 +328,7 @@ public function provideUniqueData()
*/
public function testEmptyOption(array $options, $result)
{
+ $serializedOptions = '{"key":"value"}';
$this->requestMock->expects($this->any())
->method('getParam')
->willReturnMap([
@@ -285,10 +336,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);
@@ -297,6 +354,11 @@ public function testEmptyOption(array $options, $result)
->method('loadByCode')
->willReturnSelf();
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('isValid')
+ ->with('test_attribute_code')
+ ->willReturn(true);
+
$this->resultJsonFactoryMock->expects($this->once())
->method('create')
->willReturn($this->resultJson);
@@ -320,8 +382,10 @@ public function provideEmptyOption()
return [
'empty admin scope options' => [
[
- 'value' => [
- "option_0" => [''],
+ 'option' => [
+ 'value' => [
+ "option_0" => [''],
+ ],
],
],
(object) [
@@ -331,8 +395,10 @@ public function provideEmptyOption()
],
'not empty admin scope options' => [
[
- 'value' => [
- "option_0" => ['asdads'],
+ 'option' => [
+ 'value' => [
+ "option_0" => ['asdads'],
+ ],
],
],
(object) [
@@ -341,11 +407,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 +422,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 +438,136 @@ 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->attributeCodeValidatorMock
+ ->method('isValid')
+ ->willReturn(true);
+
+ $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();
+ }
+
+ /**
+ * Test execute with an invalid attribute code
+ *
+ * @dataProvider provideInvalidAttributeCodes
+ * @param string $attributeCode
+ * @param $result
+ * @throws \Magento\Framework\Exception\NotFoundException
+ */
+ public function testExecuteWithInvalidAttributeCode($attributeCode, $result)
+ {
+ $serializedOptions = '{"key":"value"}';
+ $this->requestMock->expects($this->any())
+ ->method('getParam')
+ ->willReturnMap([
+ ['frontend_label', null, null],
+ ['frontend_input', 'select', 'multipleselect'],
+ ['attribute_code', null, $attributeCode],
+ ['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)
+ ->willReturn(["key" => "value"]);
+
+ $this->objectManagerMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->attributeMock);
+
+ $this->attributeMock->expects($this->once())
+ ->method('loadByCode')
+ ->willReturnSelf();
+
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('isValid')
+ ->with($attributeCode)
+ ->willReturn(false);
+
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('getMessages')
+ ->willReturn(['Invalid Attribute Code.']);
+
+ $this->resultJsonFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->resultJson);
+
+ $this->resultJson->expects($this->once())
+ ->method('setJsonData')
+ ->willReturnArgument(0);
+
+ $response = $this->getModel()->execute();
+ $responseObject = json_decode($response);
+
+ $this->assertEquals($responseObject, $result);
+ }
+
+ /**
+ * Providing invalid attribute codes
+ *
+ * @return array
+ */
+ public function provideInvalidAttributeCodes()
+ {
+ return [
+ 'invalid attribute code' => [
+ '.attribute_code',
+ (object) [
+ 'error' => true,
+ 'message' => 'Invalid Attribute Code.',
+ ]
+ ]
+ ];
+ }
}
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 b85b03852b62..2a75773754fc 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 ff44a91a6499..c889c58e3df3 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/Controller/Category/ViewTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php
index d93520297e48..60c6f2f1bd82 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Category/ViewTest.php
@@ -124,7 +124,7 @@ protected function setUp()
->disableOriginalConstructor()->getMock();
$this->pageConfig->expects($this->any())->method('addBodyClass')->will($this->returnSelf());
- $this->page = $this->getMockBuilder(\Magento\Framework\View\Page::class)
+ $this->page = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class)
->setMethods(['getConfig', 'initLayout', 'addPageLayoutHandles', 'getLayout', 'addUpdate'])
->disableOriginalConstructor()->getMock();
$this->page->expects($this->any())->method('getConfig')->will($this->returnValue($this->pageConfig));
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/SaveHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/SaveHandlerTest.php
new file mode 100644
index 000000000000..fbb96933db51
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/SaveHandlerTest.php
@@ -0,0 +1,178 @@
+objectManager = new ObjectManager($this);
+ $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getStore'])
+ ->getMockForAbstractClass();
+ $this->attributeRepository = $this->getMockBuilder(ProductAttributeRepositoryInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['get'])
+ ->getMockForAbstractClass();
+ $this->groupManagement = $this->getMockBuilder(GroupManagementInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getAllCustomersGroup'])
+ ->getMockForAbstractClass();
+ $this->metadataPoll = $this->getMockBuilder(MetadataPool::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getMetadata'])
+ ->getMock();
+ $this->tierPriceResource = $this->getMockBuilder(Tierprice::class)
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->saveHandler = $this->objectManager->getObject(
+ \Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\SaveHandler::class,
+ [
+ 'storeManager' => $this->storeManager,
+ 'attributeRepository' => $this->attributeRepository,
+ 'groupManagement' => $this->groupManagement,
+ 'metadataPoll' => $this->metadataPoll,
+ 'tierPriceResource' => $this->tierPriceResource
+ ]
+ );
+ }
+
+ public function testExecute(): void
+ {
+ $tierPrices = [
+ ['website_id' => 0, 'price_qty' => 2, 'cust_group' => 0, 'price' => 10],
+ ['website_id' => 0, 'price_qty' => 3, 'cust_group' => 3200, 'price' => null, 'percentage_value' => 20]
+ ];
+ $linkField = 'entity_id';
+ $productId = 10;
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject $product */
+ $product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getData','setData', 'getStoreId'])
+ ->getMockForAbstractClass();
+ $product->expects($this->atLeastOnce())->method('getData')->willReturnMap(
+ [
+ ['tier_price', $tierPrices],
+ ['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)
+ ->disableOriginalConstructor()
+ ->setMethods(['getWebsiteId'])
+ ->getMockForAbstractClass();
+ $store->expects($this->atLeastOnce())->method('getWebsiteId')->willReturn(0);
+ $this->storeManager->expects($this->atLeastOnce())->method('getStore')->willReturn($store);
+ /** @var \PHPUnit_Framework_MockObject_MockObject $attribute */
+ $attribute = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getName', 'isScopeGlobal'])
+ ->getMockForAbstractClass();
+ $attribute->expects($this->atLeastOnce())->method('getName')->willReturn('tier_price');
+ $attribute->expects($this->atLeastOnce())->method('isScopeGlobal')->willReturn(true);
+ $this->attributeRepository->expects($this->atLeastOnce())->method('get')->with('tier_price')
+ ->willReturn($attribute);
+ $productMetadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getLinkField'])
+ ->getMockForAbstractClass();
+ $productMetadata->expects($this->atLeastOnce())->method('getLinkField')->willReturn($linkField);
+ $this->metadataPoll->expects($this->atLeastOnce())->method('getMetadata')
+ ->with(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->willReturn($productMetadata);
+ $customerGroup = $this->getMockBuilder(\Magento\Customer\Api\Data\GroupInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getId'])
+ ->getMockForAbstractClass();
+ $customerGroup->expects($this->atLeastOnce())->method('getId')->willReturn(3200);
+ $this->groupManagement->expects($this->atLeastOnce())->method('getAllCustomersGroup')
+ ->willReturn($customerGroup);
+ $this->tierPriceResource->expects($this->atLeastOnce())->method('savePriceData')->willReturnSelf();
+
+ $this->assertEquals($product, $this->saveHandler->execute($product));
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Exception\InputException
+ * @expectedExceptionMessage Tier prices data should be array, but actually other type is received
+ */
+ public function testExecuteWithException(): void
+ {
+ /** @var \PHPUnit_Framework_MockObject_MockObject $attribute */
+ $attribute = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getName', 'isScopeGlobal'])
+ ->getMockForAbstractClass();
+ $attribute->expects($this->atLeastOnce())->method('getName')->willReturn('tier_price');
+ $this->attributeRepository->expects($this->atLeastOnce())->method('get')->with('tier_price')
+ ->willReturn($attribute);
+ /** @var \PHPUnit_Framework_MockObject_MockObject $product */
+ $product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getData','setData', 'getStoreId', 'getOrigData'])
+ ->getMockForAbstractClass();
+ $product->expects($this->atLeastOnce())->method('getData')->with('tier_price')->willReturn(1);
+
+ $this->saveHandler->execute($product);
+ }
+}
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
new file mode 100644
index 000000000000..cce00c50d37a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php
@@ -0,0 +1,192 @@
+objectManager = new ObjectManager($this);
+ $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getStore'])
+ ->getMockForAbstractClass();
+ $this->attributeRepository = $this->getMockBuilder(ProductAttributeRepositoryInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['get'])
+ ->getMockForAbstractClass();
+ $this->groupManagement = $this->getMockBuilder(GroupManagementInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getAllCustomersGroup'])
+ ->getMockForAbstractClass();
+ $this->metadataPoll = $this->getMockBuilder(MetadataPool::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getMetadata'])
+ ->getMock();
+ $this->tierPriceResource = $this->getMockBuilder(Tierprice::class)
+ ->disableOriginalConstructor()
+ ->setMethods([])
+ ->getMock();
+
+ $this->updateHandler = $this->objectManager->getObject(
+ \Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\UpdateHandler::class,
+ [
+ 'storeManager' => $this->storeManager,
+ 'attributeRepository' => $this->attributeRepository,
+ 'groupManagement' => $this->groupManagement,
+ 'metadataPoll' => $this->metadataPoll,
+ 'tierPriceResource' => $this->tierPriceResource
+ ]
+ );
+ }
+
+ public function testExecute(): void
+ {
+ $newTierPrices = [
+ ['website_id' => 0, 'price_qty' => 2, 'cust_group' => 0, 'price' => 15],
+ ['website_id' => 0, 'price_qty' => 3, 'cust_group' => 3200, 'price' => null, 'percentage_value' => 20]
+ ];
+ $priceIdToDelete = 2;
+ $originalTierPrices = [
+ ['price_id' => 1, 'website_id' => 0, 'price_qty' => 2, 'cust_group' => 0, 'price' => 10],
+ ['price_id' => $priceIdToDelete, 'website_id' => 0, 'price_qty' => 4, 'cust_group' => 0, 'price' => 20],
+ ];
+ $linkField = 'entity_id';
+ $productId = 10;
+
+ /** @var \PHPUnit_Framework_MockObject_MockObject $product */
+ $product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getData','setData', 'getStoreId', 'getOrigData'])
+ ->getMockForAbstractClass();
+ $product->expects($this->atLeastOnce())->method('getData')->willReturnMap(
+ [
+ ['tier_price', $newTierPrices],
+ ['entity_id', $productId]
+ ]
+ );
+ $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)
+ ->disableOriginalConstructor()
+ ->setMethods(['getWebsiteId'])
+ ->getMockForAbstractClass();
+ $store->expects($this->atLeastOnce())->method('getWebsiteId')->willReturn(0);
+ $this->storeManager->expects($this->atLeastOnce())->method('getStore')->willReturn($store);
+ /** @var \PHPUnit_Framework_MockObject_MockObject $attribute */
+ $attribute = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getName', 'isScopeGlobal'])
+ ->getMockForAbstractClass();
+ $attribute->expects($this->atLeastOnce())->method('getName')->willReturn('tier_price');
+ $attribute->expects($this->atLeastOnce())->method('isScopeGlobal')->willReturn(true);
+ $this->attributeRepository->expects($this->atLeastOnce())->method('get')->with('tier_price')
+ ->willReturn($attribute);
+ $productMetadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getLinkField'])
+ ->getMockForAbstractClass();
+ $productMetadata->expects($this->atLeastOnce())->method('getLinkField')->willReturn($linkField);
+ $this->metadataPoll->expects($this->atLeastOnce())->method('getMetadata')
+ ->with(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->willReturn($productMetadata);
+ $customerGroup = $this->getMockBuilder(\Magento\Customer\Api\Data\GroupInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getId'])
+ ->getMockForAbstractClass();
+ $customerGroup->expects($this->atLeastOnce())->method('getId')->willReturn(3200);
+ $this->groupManagement->expects($this->atLeastOnce())->method('getAllCustomersGroup')
+ ->willReturn($customerGroup);
+ $this->tierPriceResource->expects($this->exactly(2))->method('savePriceData')->willReturnSelf();
+ $this->tierPriceResource->expects($this->once())->method('deletePriceData')
+ ->with($productId, null, $priceIdToDelete);
+
+ $this->assertEquals($product, $this->updateHandler->execute($product));
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Exception\InputException
+ * @expectedExceptionMessage Tier prices data should be array, but actually other type is received
+ */
+ public function testExecuteWithException(): void
+ {
+ /** @var \PHPUnit_Framework_MockObject_MockObject $attribute */
+ $attribute = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductAttributeInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getName', 'isScopeGlobal'])
+ ->getMockForAbstractClass();
+ $attribute->expects($this->atLeastOnce())->method('getName')->willReturn('tier_price');
+ $this->attributeRepository->expects($this->atLeastOnce())->method('get')->with('tier_price')
+ ->willReturn($attribute);
+ /** @var \PHPUnit_Framework_MockObject_MockObject $product */
+ $product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getData','setData', 'getStoreId', 'getOrigData'])
+ ->getMockForAbstractClass();
+ $product->expects($this->atLeastOnce())->method('getData')->with('tier_price')->willReturn(1);
+
+ $this->updateHandler->execute($product);
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Product/PositionResolverTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Product/PositionResolverTest.php
index 1ff3a1bae5c2..7ad8b1a0ab3f 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Category/Product/PositionResolverTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Product/PositionResolverTest.php
@@ -107,7 +107,7 @@ public function testGetPositions()
$this->select->expects($this->once())
->method('where')
->willReturnSelf();
- $this->select->expects($this->once())
+ $this->select->expects($this->exactly(2))
->method('order')
->willReturnSelf();
$this->select->expects($this->once())
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/TreeTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/TreeTest.php
index 9fb2adb2b8ec..97c098ba0ff2 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Category/TreeTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/TreeTest.php
@@ -43,6 +43,11 @@ class TreeTest extends \PHPUnit\Framework\TestCase
*/
protected $node;
+ /**
+ * @var \Magento\Catalog\Model\ResourceModel\Category\TreeFactory
+ */
+ private $treeResourceFactoryMock;
+
protected function setUp()
{
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
@@ -59,6 +64,12 @@ protected function setUp()
\Magento\Store\Model\StoreManagerInterface::class
)->disableOriginalConstructor()->getMock();
+ $this->treeResourceFactoryMock = $this->createMock(
+ \Magento\Catalog\Model\ResourceModel\Category\TreeFactory::class
+ );
+ $this->treeResourceFactoryMock->method('create')
+ ->willReturn($this->categoryTreeMock);
+
$methods = ['create'];
$this->treeFactoryMock =
$this->createPartialMock(\Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory::class, $methods);
@@ -70,7 +81,8 @@ protected function setUp()
'categoryCollection' => $this->categoryCollection,
'categoryTree' => $this->categoryTreeMock,
'storeManager' => $this->storeManagerMock,
- 'treeFactory' => $this->treeFactoryMock
+ 'treeFactory' => $this->treeFactoryMock,
+ 'treeResourceFactory' => $this->treeResourceFactoryMock,
]
);
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php
index 64eedbce2d98..b4042d6b02c1 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php
@@ -383,13 +383,16 @@ public function testReindexFlatEnabled(
public function reindexFlatDisabledTestDataProvider()
{
return [
- [false, null, null, null, 0],
- [true, null, null, null, 0],
- [false, [], null, null, 0],
- [false, ["1", "2"], null, null, 1],
- [false, null, 1, null, 1],
- [false, ["1", "2"], 0, 1, 1],
- [false, null, 1, 1, 0],
+ [false, null, null, null, null, null, 0],
+ [true, null, null, null, null, null, 0],
+ [false, [], null, null, null, null, 0],
+ [false, ["1", "2"], null, null, null, null, 1],
+ [false, null, 1, null, null, null, 1],
+ [false, ["1", "2"], 0, 1, null, null, 1],
+ [false, null, 1, 1, null, null, 0],
+ [false, ["1", "2"], null, null, 0, 1, 1],
+ [false, ["1", "2"], null, null, 1, 0, 1],
+
];
}
@@ -407,11 +410,16 @@ public function testReindexFlatDisabled(
$affectedIds,
$isAnchorOrig,
$isAnchor,
+ $isActiveOrig,
+ $isActive,
$expectedProductReindexCall
) {
$this->category->setAffectedProductIds($affectedIds);
$this->category->setData('is_anchor', $isAnchor);
$this->category->setOrigData('is_anchor', $isAnchorOrig);
+ $this->category->setData('is_active', $isActive);
+ $this->category->setOrigData('is_active', $isActiveOrig);
+
$this->category->setAffectedProductIds($affectedIds);
$pathIds = ['path/1/2', 'path/2/3'];
@@ -422,7 +430,7 @@ public function testReindexFlatDisabled(
->method('isFlatEnabled')
->will($this->returnValue(false));
- $this->productIndexer->expects($this->exactly(1))
+ $this->productIndexer
->method('isScheduled')
->willReturn($productScheduled);
$this->productIndexer->expects($this->exactly($expectedProductReindexCall))
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php
index d8931cbbfcf7..f0e17c7938b2 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php
@@ -57,10 +57,14 @@ public function testGetCollection()
$linkedProductOneMock = $this->createMock(Product::class);
$linkedProductTwoMock = $this->createMock(Product::class);
$linkedProductThreeMock = $this->createMock(Product::class);
+ $linkedProductFourMock = $this->createMock(Product::class);
+ $linkedProductFiveMock = $this->createMock(Product::class);
$linkedProductOneMock->expects($this->once())->method('getId')->willReturn(1);
$linkedProductTwoMock->expects($this->once())->method('getId')->willReturn(2);
$linkedProductThreeMock->expects($this->once())->method('getId')->willReturn(3);
+ $linkedProductFourMock->expects($this->once())->method('getId')->willReturn(4);
+ $linkedProductFiveMock->expects($this->once())->method('getId')->willReturn(5);
$this->converterPoolMock->expects($this->once())
->method('getConverter')
@@ -71,9 +75,11 @@ public function testGetCollection()
[$linkedProductOneMock, ['name' => 'Product One', 'position' => 10]],
[$linkedProductTwoMock, ['name' => 'Product Two', 'position' => 2]],
[$linkedProductThreeMock, ['name' => 'Product Three', 'position' => 2]],
+ [$linkedProductFourMock, ['name' => 'Product Four', 'position' => null]],
+ [$linkedProductFiveMock, ['name' => 'Product Five']],
];
- $this->converterMock->expects($this->exactly(3))->method('convert')->willReturnMap($map);
+ $this->converterMock->expects($this->exactly(5))->method('convert')->willReturnMap($map);
$this->providerMock->expects($this->once())
->method('getLinkedProducts')
@@ -82,14 +88,18 @@ public function testGetCollection()
[
$linkedProductOneMock,
$linkedProductTwoMock,
- $linkedProductThreeMock
+ $linkedProductThreeMock,
+ $linkedProductFourMock,
+ $linkedProductFiveMock,
]
);
$expectedResult = [
- 2 => ['name' => 'Product Two', 'position' => 2],
- 3 => ['name' => 'Product Three', 'position' => 2],
- 10 => ['name' => 'Product One', 'position' => 10],
+ 0 => ['name' => 'Product Four', 'position' => 0],
+ 1 => ['name' => 'Product Five', 'position' => 0],
+ 2 => ['name' => 'Product Three', 'position' => 2],
+ 3 => ['name' => 'Product Two', 'position' => 2],
+ 4 => ['name' => 'Product One', 'position' => 10],
];
$actualResult = $this->model->getCollection($this->productMock, 'crosssell');
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php
index 5b1d3bf7943f..23f0aec5b69a 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php
@@ -9,6 +9,11 @@
use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+/**
+ * Tests \Magento\Catalog\Model\Config\CatalogClone\Media\Image.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class ImageTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -36,6 +41,14 @@ class ImageTest extends \PHPUnit\Framework\TestCase
*/
private $attribute;
+ /**
+ * @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $escaperMock;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class)
@@ -62,54 +75,79 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
+ $this->escaperMock = $this->getMockBuilder(
+ \Magento\Framework\Escaper::class
+ )
+ ->disableOriginalConstructor()
+ ->setMethods(['escapeHtml'])
+ ->getMock();
+
$helper = new ObjectManager($this);
$this->model = $helper->getObject(
\Magento\Catalog\Model\Config\CatalogClone\Media\Image::class,
[
'eavConfig' => $this->eavConfig,
- 'attributeCollectionFactory' => $this->attributeCollectionFactory
+ 'attributeCollectionFactory' => $this->attributeCollectionFactory,
+ 'escaper' => $this->escaperMock,
]
);
}
- public function testGetPrefixes()
+ /**
+ * @param string $actualLabel
+ * @param string $expectedLabel
+ * @return void
+ *
+ * @dataProvider getPrefixesDataProvider
+ */
+ public function testGetPrefixes(string $actualLabel, string $expectedLabel): void
{
$entityTypeId = 3;
/** @var \Magento\Eav\Model\Entity\Type|\PHPUnit_Framework_MockObject_MockObject $entityType */
$entityType = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class)
->disableOriginalConstructor()
->getMock();
- $entityType->expects($this->once())->method('getId')->will($this->returnValue($entityTypeId));
+ $entityType->expects($this->once())->method('getId')->willReturn($entityTypeId);
/** @var AbstractFrontend|\PHPUnit_Framework_MockObject_MockObject $frontend */
$frontend = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend::class)
->setMethods(['getLabel'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $frontend->expects($this->once())->method('getLabel')->will($this->returnValue('testLabel'));
+ $frontend->expects($this->once())->method('getLabel')->willReturn($actualLabel);
- $this->attributeCollection->expects($this->once())->method('setEntityTypeFilter')->with(
- $this->equalTo($entityTypeId)
- );
- $this->attributeCollection->expects($this->once())->method('setFrontendInputTypeFilter')->with(
- $this->equalTo('media_image')
- );
+ $this->attributeCollection->expects($this->once())->method('setEntityTypeFilter')->with($entityTypeId);
+ $this->attributeCollection->expects($this->once())->method('setFrontendInputTypeFilter')->with('media_image');
- $this->attribute->expects($this->once())->method('getAttributeCode')->will(
- $this->returnValue('attributeCode')
- );
- $this->attribute->expects($this->once())->method('getFrontend')->will(
- $this->returnValue($frontend)
- );
+ $this->attribute->expects($this->once())->method('getAttributeCode')->willReturn('attributeCode');
+ $this->attribute->expects($this->once())->method('getFrontend')->willReturn($frontend);
- $this->attributeCollection->expects($this->any())->method('getIterator')->will(
- $this->returnValue(new \ArrayIterator([$this->attribute]))
- );
+ $this->attributeCollection->expects($this->any())->method('getIterator')
+ ->willReturn(new \ArrayIterator([$this->attribute]));
+
+ $this->eavConfig->expects($this->any())->method('getEntityType')->with(Product::ENTITY)
+ ->willReturn($entityType);
- $this->eavConfig->expects($this->any())->method('getEntityType')->with(
- $this->equalTo(Product::ENTITY)
- )->will($this->returnValue($entityType));
+ $this->escaperMock->expects($this->once())->method('escapeHtml')->with($actualLabel)
+ ->willReturn($expectedLabel);
- $this->assertEquals([['field' => 'attributeCode_', 'label' => 'testLabel']], $this->model->getPrefixes());
+ $this->assertEquals([['field' => 'attributeCode_', 'label' => $expectedLabel]], $this->model->getPrefixes());
+ }
+
+ /**
+ * @return array
+ */
+ public function getPrefixesDataProvider(): array
+ {
+ return [
+ [
+ 'actual_label' => 'testLabel',
+ 'expected_label' => 'testLabel',
+ ],
+ [
+ 'actual_label' => ' '<media-image-attributelabel',
+ ],
+ ];
}
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php
index c989f2dd4746..6552e8544000 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/Eav/Action/FullTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php
index 90c3f999a6a8..2e1cff834fd3 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php
@@ -3,15 +3,29 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Eav\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Indexer\Product\Eav\Action\Full;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Decimal;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Source;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Query\Generator;
+use Magento\Framework\DB\Select;
+use Magento\Framework\EntityManager\EntityMetadataInterface;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Indexer\BatchProviderInterface;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator;
+use PHPUnit\Framework\MockObject\MockObject as MockObject;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -19,45 +33,50 @@
class FullTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full|\PHPUnit_Framework_MockObject_MockObject
+ * @var Full|MockObject
*/
private $model;
/**
- * @var DecimalFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var DecimalFactory|MockObject
*/
private $eavDecimalFactory;
/**
- * @var SourceFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var SourceFactory|MockObject
*/
private $eavSourceFactory;
/**
- * @var MetadataPool|\PHPUnit_Framework_MockObject_MockObject
+ * @var MetadataPool|MockObject
*/
private $metadataPool;
/**
- * @var BatchProviderInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var BatchProviderInterface|MockObject
*/
private $batchProvider;
/**
- * @var BatchSizeCalculator|\PHPUnit_Framework_MockObject_MockObject
+ * @var BatchSizeCalculator|MockObject
*/
private $batchSizeCalculator;
/**
- * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject
+ * @var ActiveTableSwitcher|MockObject
*/
private $activeTableSwitcher;
/**
- * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var ScopeConfigInterface|MockObject
*/
private $scopeConfig;
+ /**
+ * @var Generator
+ */
+ private $batchQueryGenerator;
+
/**
* @return void
*/
@@ -67,15 +86,16 @@ protected function setUp()
$this->eavSourceFactory = $this->createPartialMock(SourceFactory::class, ['create']);
$this->metadataPool = $this->createMock(MetadataPool::class);
$this->batchProvider = $this->getMockForAbstractClass(BatchProviderInterface::class);
+ $this->batchQueryGenerator = $this->createMock(Generator::class);
$this->batchSizeCalculator = $this->createMock(BatchSizeCalculator::class);
$this->activeTableSwitcher = $this->createMock(ActiveTableSwitcher::class);
- $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
$objectManager = new ObjectManager($this);
$this->model = $objectManager->getObject(
- \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full::class,
+ Full::class,
[
'eavDecimalFactory' => $this->eavDecimalFactory,
'eavSourceFactory' => $this->eavSourceFactory,
@@ -83,7 +103,8 @@ protected function setUp()
'batchProvider' => $this->batchProvider,
'batchSizeCalculator' => $this->batchSizeCalculator,
'activeTableSwitcher' => $this->activeTableSwitcher,
- 'scopeConfig' => $this->scopeConfig
+ 'scopeConfig' => $this->scopeConfig,
+ 'batchQueryGenerator' => $this->batchQueryGenerator,
]
);
}
@@ -96,15 +117,15 @@ public function testExecute()
$this->scopeConfig->expects($this->once())->method('getValue')->willReturn(1);
$ids = [1, 2, 3];
- $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)
+ $connectionMock = $this->getMockBuilder(AdapterInterface::class)
->getMockForAbstractClass();
$connectionMock->expects($this->atLeastOnce())->method('describeTable')->willReturn(['id' => []]);
- $eavSource = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Source::class)
+ $eavSource = $this->getMockBuilder(Source::class)
->disableOriginalConstructor()
->getMock();
- $eavDecimal = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Decimal::class)
+ $eavDecimal = $this->getMockBuilder(Decimal::class)
->disableOriginalConstructor()
->getMock();
@@ -125,22 +146,28 @@ public function testExecute()
$this->eavSourceFactory->expects($this->once())->method('create')->will($this->returnValue($eavDecimal));
- $entityMetadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class)
+ $entityMetadataMock = $this->getMockBuilder(EntityMetadataInterface::class)
->getMockForAbstractClass();
$this->metadataPool->expects($this->atLeastOnce())
->method('getMetadata')
- ->with(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->with(ProductInterface::class)
->willReturn($entityMetadataMock);
- $this->batchProvider->expects($this->atLeastOnce())
- ->method('getBatches')
- ->willReturn([['from' => 10, 'to' => 100]]);
- $this->batchProvider->expects($this->atLeastOnce())
- ->method('getBatchIds')
+ // Super inefficient algorithm in some cases
+ $this->batchProvider->expects($this->never())
+ ->method('getBatches');
+
+ $batchQuery = $this->createMock(Select::class);
+
+ $connectionMock->method('fetchCol')
+ ->with($batchQuery)
->willReturn($ids);
- $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
+ $this->batchQueryGenerator->method('generate')
+ ->willReturn([$batchQuery]);
+
+ $selectMock = $this->getMockBuilder(Select::class)
->disableOriginalConstructor()
->getMock();
@@ -153,7 +180,7 @@ public function testExecute()
/**
* @return void
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
public function testExecuteWithDisabledEavIndexer()
{
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 cc6f5d84ef00..e1e2816d4422 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/EraserTest.php
@@ -53,8 +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('where')->with('entity_id IN(?)', $productsToDeleteIds)
+ $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('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 7b2a2ff304b8..11d07872fef9 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php
@@ -6,8 +6,13 @@
namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Flat\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class RowTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -16,76 +21,116 @@ class RowTest extends \PHPUnit\Framework\TestCase
protected $model;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $storeManager;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $store;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $productIndexerHelper;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $resource;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $connection;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $flatItemWriter;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $flatItemEraser;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
protected $flatTableBuilder;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$objectManager = new ObjectManager($this);
+ $attributeTable = 'catalog_product_entity_int';
+ $statusId = 22;
$this->connection = $this->createMock(\Magento\Framework\DB\Adapter\AdapterInterface::class);
$this->resource = $this->createMock(\Magento\Framework\App\ResourceConnection::class);
$this->resource->expects($this->any())->method('getConnection')
->with('default')
- ->will($this->returnValue($this->connection));
+ ->willReturn($this->connection);
$this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
$this->store = $this->createMock(\Magento\Store\Model\Store::class);
- $this->store->expects($this->any())->method('getId')->will($this->returnValue('store_id_1'));
- $this->storeManager->expects($this->any())->method('getStores')->will($this->returnValue([$this->store]));
- $this->productIndexerHelper = $this->createMock(\Magento\Catalog\Helper\Product\Flat\Indexer::class);
+ $this->store->expects($this->any())->method('getId')->willReturn('store_id_1');
+ $this->storeManager->expects($this->any())->method('getStores')->willReturn([$this->store]);
$this->flatItemEraser = $this->createMock(\Magento\Catalog\Model\Indexer\Product\Flat\Action\Eraser::class);
$this->flatItemWriter = $this->createMock(\Magento\Catalog\Model\Indexer\Product\Flat\Action\Indexer::class);
$this->flatTableBuilder = $this->createMock(
\Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder::class
);
+ $this->productIndexerHelper = $this->createMock(\Magento\Catalog\Helper\Product\Flat\Indexer::class);
+ $statusAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->productIndexerHelper->expects($this->any())->method('getAttribute')
+ ->with('status')
+ ->willReturn($statusAttributeMock);
+ $backendMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $backendMock->expects($this->any())->method('getTable')->willReturn($attributeTable);
+ $statusAttributeMock->expects($this->any())->method('getBackend')->willReturn($backendMock);
+ $statusAttributeMock->expects($this->any())->method('getId')->willReturn($statusId);
+ $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->connection->expects($this->any())->method('select')->willReturn($selectMock);
+ $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();
+ $pdoMock = $this->createMock(\Zend_Db_Statement_Pdo::class);
+ $this->connection->expects($this->any())->method('query')->with($selectMock)->willReturn($pdoMock);
+ $pdoMock->expects($this->any())->method('fetchColumn')->willReturn('1');
+
+ $metadataPool = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class);
+ $productMetadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class)
+ ->getMockForAbstractClass();
+ $metadataPool->expects($this->any())->method('getMetadata')->with(ProductInterface::class)
+ ->willReturn($productMetadata);
+ $productMetadata->expects($this->any())->method('getLinkField')->willReturn('entity_id');
$this->model = $objectManager->getObject(
\Magento\Catalog\Model\Indexer\Product\Flat\Action\Row::class,
[
- 'resource' => $this->resource,
- 'storeManager' => $this->storeManager,
- 'productHelper' => $this->productIndexerHelper,
- 'flatItemEraser' => $this->flatItemEraser,
- 'flatItemWriter' => $this->flatItemWriter,
- 'flatTableBuilder' => $this->flatTableBuilder
+ 'resource' => $this->resource,
+ 'storeManager' => $this->storeManager,
+ 'productHelper' => $this->productIndexerHelper,
+ 'flatItemEraser' => $this->flatItemEraser,
+ 'flatItemWriter' => $this->flatItemWriter,
+ 'flatTableBuilder' => $this->flatTableBuilder,
]
);
+
+ $objectManager->setBackwardCompatibleProperty($this->model, 'metadataPool', $metadataPool);
}
/**
@@ -100,9 +145,9 @@ public function testExecuteWithEmptyId()
public function testExecuteWithNonExistingFlatTablesCreatesTables()
{
$this->productIndexerHelper->expects($this->any())->method('getFlatTableName')
- ->will($this->returnValue('store_flat_table'));
+ ->willReturn('store_flat_table');
$this->connection->expects($this->any())->method('isTableExists')->with('store_flat_table')
- ->will($this->returnValue(false));
+ ->willReturn(false);
$this->flatItemEraser->expects($this->never())->method('removeDeletedProducts');
$this->flatTableBuilder->expects($this->once())->method('build')->with('store_id_1', ['product_id_1']);
$this->flatItemWriter->expects($this->once())->method('write')->with('store_id_1', 'product_id_1');
@@ -112,9 +157,9 @@ public function testExecuteWithNonExistingFlatTablesCreatesTables()
public function testExecuteWithExistingFlatTablesCreatesTables()
{
$this->productIndexerHelper->expects($this->any())->method('getFlatTableName')
- ->will($this->returnValue('store_flat_table'));
+ ->willReturn('store_flat_table');
$this->connection->expects($this->any())->method('isTableExists')->with('store_flat_table')
- ->will($this->returnValue(true));
+ ->willReturn(true);
$this->flatItemEraser->expects($this->once())->method('removeDeletedProducts');
$this->flatTableBuilder->expects($this->never())->method('build')->with('store_id_1', ['product_id_1']);
$this->model->execute('product_id_1');
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Layer/Filter/DataProvider/PriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Layer/Filter/DataProvider/PriceTest.php
index f2c77627e38d..8ca23df31cde 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Layer/Filter/DataProvider/PriceTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Layer/Filter/DataProvider/PriceTest.php
@@ -152,7 +152,7 @@ public function testGetMaxPrice()
$this->productCollection->expects($this->once())
->method('getMaxPrice')
->will($this->returnValue($maxPrice));
- $this->assertSame(floatval($maxPrice), $this->target->getMaxPrice());
+ $this->assertSame((float)$maxPrice, $this->target->getMaxPrice());
}
/**
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 31fd0696db32..80b6db2a516b 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
@@ -6,9 +6,12 @@
namespace Magento\Catalog\Test\Unit\Model\Product;
use Magento\Catalog\Api\Data\ProductInterface;
-use \Magento\Catalog\Model\Product\Copier;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Copier;
/**
+ * Test for Magento\Catalog\Model\Product\Copier class.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CopierTest extends \PHPUnit\Framework\TestCase
@@ -54,7 +57,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)
@@ -75,6 +78,9 @@ protected function setUp()
]);
}
+ /**
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
public function testCopy()
{
$stockItem = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class)
@@ -102,11 +108,47 @@ public function testCopy()
['linkField', null, '1'],
]);
- $resourceMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Product::class);
- $this->productMock->expects($this->once())->method('getResource')->will($this->returnValue($resourceMock));
+ $entityMock = $this->getMockForAbstractClass(
+ \Magento\Eav\Model\Entity\AbstractEntity::class,
+ [],
+ '',
+ false,
+ true,
+ true,
+ ['checkAttributeUniqueValue']
+ );
+ $entityMock->expects($this->any())
+ ->method('checkAttributeUniqueValue')
+ ->willReturn(true);
+
+ $attributeMock = $this->getMockForAbstractClass(
+ \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class,
+ [],
+ '',
+ false,
+ true,
+ true,
+ ['getEntity']
+ );
+ $attributeMock->expects($this->any())
+ ->method('getEntity')
+ ->willReturn($entityMock);
+
+ $resourceMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getAttributeRawValue', 'duplicate', 'getAttribute'])
+ ->getMock();
+ $resourceMock->expects($this->any())
+ ->method('getAttributeRawValue')
+ ->willReturn('urk-key-1');
+ $resourceMock->expects($this->any())
+ ->method('getAttribute')
+ ->willReturn($attributeMock);
+
+ $this->productMock->expects($this->any())->method('getResource')->will($this->returnValue($resourceMock));
$duplicateMock = $this->createPartialMock(
- \Magento\Catalog\Model\Product::class,
+ Product::class,
[
'__wakeup',
'setData',
@@ -118,11 +160,11 @@ public function testCopy()
'setCreatedAt',
'setUpdatedAt',
'setId',
- 'setStoreId',
'getEntityId',
'save',
'setUrlKey',
- 'getUrlKey',
+ 'setStoreId',
+ 'getStoreIds',
]
);
$this->productFactoryMock->expects($this->once())->method('create')->will($this->returnValue($duplicateMock));
@@ -137,27 +179,22 @@ public function testCopy()
)->with(
\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED
);
+ $duplicateMock->expects($this->atLeastOnce())->method('setStoreId');
$duplicateMock->expects($this->once())->method('setCreatedAt')->with(null);
$duplicateMock->expects($this->once())->method('setUpdatedAt')->with(null);
$duplicateMock->expects($this->once())->method('setId')->with(null);
- $duplicateMock->expects(
- $this->once()
- )->method(
- 'setStoreId'
- )->with(
- \Magento\Store\Model\Store::DEFAULT_STORE_ID
- );
- $duplicateMock->expects($this->once())->method('setData')->with($productData);
+ $duplicateMock->expects($this->atLeastOnce())->method('getStoreIds')->willReturn([]);
+ $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');
$duplicateMock->expects($this->any())->method('getData')->willReturnMap([
['linkField', null, '2'],
- ]); $this->optionRepositoryMock->expects($this->once())
+ ]);
+ $this->optionRepositoryMock->expects($this->once())
->method('duplicate')
->with($this->productMock, $duplicateMock);
$resourceMock->expects($this->once())->method('duplicate')->with(1, 2);
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 9fafbc9d9675..1d12645019d1 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/Option/Validator/DefaultValidatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php
index da6b790fedfa..7c2ec8abb768 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/DefaultValidatorTest.php
@@ -18,6 +18,11 @@ class DefaultValidatorTest extends \PHPUnit\Framework\TestCase
*/
protected $valueMock;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $localeFormatMock;
+
/**
* @inheritdoc
*/
@@ -26,6 +31,8 @@ protected function setUp()
$configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class);
$storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
$priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock);
+ $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class);
+
$config = [
[
'label' => 'group label 1',
@@ -51,7 +58,8 @@ protected function setUp()
$configMock->expects($this->once())->method('getAll')->will($this->returnValue($config));
$this->validator = new \Magento\Catalog\Model\Product\Option\Validator\DefaultValidator(
$configMock,
- $priceConfigMock
+ $priceConfigMock,
+ $this->localeFormatMock
);
}
@@ -63,10 +71,10 @@ public function isValidTitleDataProvider()
{
$mess = ['option required fields' => 'Missed values for option required fields'];
return [
- ['option_title', 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 1]), [], true],
- ['option_title', 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 0]), [], true],
- [null, 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 1]), [], true],
- [null, 'name 1.1', 'fixed', new \Magento\Framework\DataObject(['store_id' => 0]), $mess, false],
+ ['option_title', 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 1]), [], true],
+ ['option_title', 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 0]), [], true],
+ [null, 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 1]), [], true],
+ [null, 'name 1.1', 'fixed', 10, new \Magento\Framework\DataObject(['store_id' => 0]), $mess, false],
];
}
@@ -79,15 +87,18 @@ public function isValidTitleDataProvider()
* @param bool $result
* @dataProvider isValidTitleDataProvider
*/
- public function testIsValidTitle($title, $type, $priceType, $product, $messages, $result)
+ public function testIsValidTitle($title, $type, $priceType, $price, $product, $messages, $result)
{
- $methods = ['getTitle', 'getType', 'getPriceType', '__wakeup', 'getProduct'];
+ $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', '__wakeup', 'getProduct'];
$valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods);
$valueMock->expects($this->once())->method('getTitle')->will($this->returnValue($title));
$valueMock->expects($this->any())->method('getType')->will($this->returnValue($type));
$valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue($priceType));
- // $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price));
+ $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price));
$valueMock->expects($this->once())->method('getProduct')->will($this->returnValue($product));
+
+ $this->localeFormatMock->expects($this->once())->method('getNumber')->will($this->returnValue($price));
+
$this->assertEquals($result, $this->validator->isValid($valueMock));
$this->assertEquals($messages, $this->validator->getMessages());
}
@@ -126,4 +137,43 @@ public function testIsValidFail($product)
$this->assertFalse($this->validator->isValid($valueMock));
$this->assertEquals($messages, $this->validator->getMessages());
}
+
+ /**
+ * Data provider for testValidationNegativePrice
+ * @return array
+ */
+ public function validationPriceDataProvider()
+ {
+ return [
+ ['option_title', 'name 1.1', 'fixed', -12, new \Magento\Framework\DataObject(['store_id' => 1])],
+ ['option_title', 'name 1.1', 'fixed', -12, new \Magento\Framework\DataObject(['store_id' => 0])],
+ ['option_title', 'name 1.1', 'fixed', 12, new \Magento\Framework\DataObject(['store_id' => 1])],
+ ['option_title', 'name 1.1', 'fixed', 12, new \Magento\Framework\DataObject(['store_id' => 0])]
+ ];
+ }
+
+ /**
+ * @param $title
+ * @param $type
+ * @param $priceType
+ * @param $price
+ * @param $product
+ * @dataProvider validationPriceDataProvider
+ */
+ public function testValidationPrice($title, $type, $priceType, $price, $product)
+ {
+ $methods = ['getTitle', 'getType', 'getPriceType', 'getPrice', '__wakeup', 'getProduct'];
+ $valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods);
+ $valueMock->expects($this->once())->method('getTitle')->will($this->returnValue($title));
+ $valueMock->expects($this->exactly(2))->method('getType')->will($this->returnValue($type));
+ $valueMock->expects($this->once())->method('getPriceType')->will($this->returnValue($priceType));
+ $valueMock->expects($this->once())->method('getPrice')->will($this->returnValue($price));
+ $valueMock->expects($this->once())->method('getProduct')->will($this->returnValue($product));
+
+ $this->localeFormatMock->expects($this->once())->method('getNumber')->will($this->returnValue($price));
+
+ $messages = [];
+ $this->assertTrue($this->validator->isValid($valueMock));
+ $this->assertEquals($messages, $this->validator->getMessages());
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php
index 2de993c07551..e688da1c6aa1 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/FileTest.php
@@ -18,6 +18,11 @@ class FileTest extends \PHPUnit\Framework\TestCase
*/
protected $valueMock;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $localeFormatMock;
+
/**
* @inheritdoc
*/
@@ -26,6 +31,8 @@ protected function setUp()
$configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class);
$storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
$priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock);
+ $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class);
+
$config = [
[
'label' => 'group label 1',
@@ -53,7 +60,8 @@ protected function setUp()
$this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods);
$this->validator = new \Magento\Catalog\Model\Product\Option\Validator\File(
$configMock,
- $priceConfigMock
+ $priceConfigMock,
+ $this->localeFormatMock
);
}
@@ -70,6 +78,15 @@ public function testIsValidSuccess()
->willReturn(10);
$this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(10));
$this->valueMock->expects($this->once())->method('getImageSizeY')->will($this->returnValue(15));
+ $this->localeFormatMock->expects($this->at(0))
+ ->method('getNumber')
+ ->with($this->equalTo(10))
+ ->will($this->returnValue(10));
+ $this->localeFormatMock
+ ->expects($this->at(2))
+ ->method('getNumber')
+ ->with($this->equalTo(15))
+ ->will($this->returnValue(15));
$this->assertEmpty($this->validator->getMessages());
$this->assertTrue($this->validator->isValid($this->valueMock));
}
@@ -87,6 +104,16 @@ public function testIsValidWithNegativeImageSize()
->willReturn(10);
$this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(-10));
$this->valueMock->expects($this->never())->method('getImageSizeY');
+ $this->localeFormatMock->expects($this->at(0))
+ ->method('getNumber')
+ ->with($this->equalTo(10))
+ ->will($this->returnValue(10));
+ $this->localeFormatMock
+ ->expects($this->at(1))
+ ->method('getNumber')
+ ->with($this->equalTo(-10))
+ ->will($this->returnValue(-10));
+
$messages = [
'option values' => 'Invalid option value',
];
@@ -107,6 +134,15 @@ public function testIsValidWithNegativeImageSizeY()
->willReturn(10);
$this->valueMock->expects($this->once())->method('getImageSizeX')->will($this->returnValue(10));
$this->valueMock->expects($this->once())->method('getImageSizeY')->will($this->returnValue(-10));
+ $this->localeFormatMock->expects($this->at(0))
+ ->method('getNumber')
+ ->with($this->equalTo(10))
+ ->will($this->returnValue(10));
+ $this->localeFormatMock
+ ->expects($this->at(2))
+ ->method('getNumber')
+ ->with($this->equalTo(-10))
+ ->will($this->returnValue(-10));
$messages = [
'option values' => 'Invalid option value',
];
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php
index b97783edf856..7fad5592a2d2 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/SelectTest.php
@@ -18,6 +18,11 @@ class SelectTest extends \PHPUnit\Framework\TestCase
*/
protected $valueMock;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $localeFormatMock;
+
/**
* @inheritdoc
*/
@@ -26,6 +31,7 @@ protected function setUp()
$configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class);
$storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
$priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock);
+ $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class);
$config = [
[
'label' => 'group label 1',
@@ -53,7 +59,8 @@ protected function setUp()
$this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods, []);
$this->validator = new \Magento\Catalog\Model\Product\Option\Validator\Select(
$configMock,
- $priceConfigMock
+ $priceConfigMock,
+ $this->localeFormatMock
);
}
@@ -69,6 +76,12 @@ public function testIsValidSuccess($expectedResult, array $value)
$this->valueMock->expects($this->never())->method('getPriceType');
$this->valueMock->expects($this->never())->method('getPrice');
$this->valueMock->expects($this->any())->method('getData')->with('values')->will($this->returnValue([$value]));
+ if (isset($value['price'])) {
+ $this->localeFormatMock
+ ->expects($this->once())
+ ->method('getNumber')
+ ->will($this->returnValue($value['price']));
+ }
$this->assertEquals($expectedResult, $this->validator->isValid($this->valueMock));
}
@@ -117,6 +130,7 @@ public function testIsValidateWithInvalidOptionValues()
->method('getData')
->with('values')
->will($this->returnValue('invalid_data'));
+
$messages = [
'option values' => 'Invalid option value',
];
@@ -159,6 +173,7 @@ public function testIsValidateWithInvalidData($priceType, $price, $title)
$this->valueMock->expects($this->never())->method('getPriceType');
$this->valueMock->expects($this->never())->method('getPrice');
$this->valueMock->expects($this->any())->method('getData')->with('values')->will($this->returnValue([$value]));
+ $this->localeFormatMock->expects($this->any())->method('getNumber')->will($this->returnValue($price));
$messages = [
'option values' => 'Invalid option value',
];
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php
index 4881154728dd..a3e6189f7492 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Validator/TextTest.php
@@ -18,6 +18,11 @@ class TextTest extends \PHPUnit\Framework\TestCase
*/
protected $valueMock;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $localeFormatMock;
+
/**
* @inheritdoc
*/
@@ -26,6 +31,7 @@ protected function setUp()
$configMock = $this->createMock(\Magento\Catalog\Model\ProductOptions\ConfigInterface::class);
$storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
$priceConfigMock = new \Magento\Catalog\Model\Config\Source\Product\Options\Price($storeManagerMock);
+ $this->localeFormatMock = $this->createMock(\Magento\Framework\Locale\FormatInterface::class);
$config = [
[
'label' => 'group label 1',
@@ -53,7 +59,8 @@ protected function setUp()
$this->valueMock = $this->createPartialMock(\Magento\Catalog\Model\Product\Option::class, $methods);
$this->validator = new \Magento\Catalog\Model\Product\Option\Validator\Text(
$configMock,
- $priceConfigMock
+ $priceConfigMock,
+ $this->localeFormatMock
);
}
@@ -69,6 +76,10 @@ public function testIsValidSuccess()
$this->valueMock->method('getPrice')
->willReturn(10);
$this->valueMock->expects($this->once())->method('getMaxCharacters')->will($this->returnValue(10));
+ $this->localeFormatMock->expects($this->exactly(2))
+ ->method('getNumber')
+ ->with($this->equalTo(10))
+ ->will($this->returnValue(10));
$this->assertTrue($this->validator->isValid($this->valueMock));
$this->assertEmpty($this->validator->getMessages());
}
@@ -85,6 +96,15 @@ public function testIsValidWithNegativeMaxCharacters()
$this->valueMock->method('getPrice')
->willReturn(10);
$this->valueMock->expects($this->once())->method('getMaxCharacters')->will($this->returnValue(-10));
+ $this->localeFormatMock->expects($this->at(0))
+ ->method('getNumber')
+ ->with($this->equalTo(10))
+ ->will($this->returnValue(10));
+ $this->localeFormatMock
+ ->expects($this->at(1))
+ ->method('getNumber')
+ ->with($this->equalTo(-10))
+ ->will($this->returnValue(-10));
$messages = [
'option values' => 'Invalid option value',
];
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
index c9288790ed6e..a97f2281125a 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Price/TierPriceStorageTest.php
@@ -152,6 +152,30 @@ public function testGet()
$this->assertEquals(2, count($prices));
}
+ /**
+ * Test get method without tierprices.
+ *
+ * @return void
+ */
+ public function testGetWithoutTierPrices()
+ {
+ $skus = ['simple', 'virtual'];
+ $this->tierPriceValidator
+ ->expects($this->once())
+ ->method('validateSkus')
+ ->with($skus)
+ ->willReturn($skus);
+ $this->productIdLocator->expects($this->atLeastOnce())
+ ->method('retrieveProductIdsBySkus')
+ ->with(['simple', 'virtual'])
+ ->willReturn(['simple' => ['2' => 'simple'], 'virtual' => ['3' => 'virtual']]);
+ $this->tierPricePersistence->expects($this->once())->method('get')->willReturn([]);
+ $this->tierPricePersistence->expects($this->never())->method('getEntityLinkField');
+ $this->tierPriceFactory->expects($this->never())->method('create');
+ $prices = $this->tierPriceStorage->get($skus);
+ $this->assertEmpty($prices);
+ }
+
/**
* Test update method.
*
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php
index 754d80302d41..6029a2b82008 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/ProductFrontendAction/SynchronizerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php
index fce4a02622d9..38bed83cb950 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php
@@ -80,6 +80,7 @@ protected function setUp()
public function testFilterProductActions()
{
+ $typeId = 'recently_compared_product';
$productsData = [
1 => [
'added_at' => 12,
@@ -87,7 +88,7 @@ public function testFilterProductActions()
],
2 => [
'added_at' => 13,
- 'product_id' => 2,
+ 'product_id' => '2',
],
3 => [
'added_at' => 14,
@@ -126,10 +127,12 @@ public function testFilterProductActions()
$collection->expects($this->once())
->method('addFilterByUserIdentities')
->with(1, 34);
- $collection->expects($this->any())
+ $collection->expects($this->at(1))
->method('addFieldToFilter')
- ->withConsecutive(['type_id'], ['product_id']);
-
+ ->with('type_id', $typeId);
+ $collection->expects($this->at(2))
+ ->method('addFieldToFilter')
+ ->with('product_id', [1, 2]);
$iterator = new \IteratorIterator(new \ArrayIterator([$frontendAction]));
$collection->expects($this->once())
->method('getIterator')
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 f340d0b204b6..ae479a9b34d4 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/ProductIdLocatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
index b730e12ca820..b9cb82274c80 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
@@ -58,7 +58,16 @@ public function testRetrieveProductIdsBySkus()
{
$skus = ['sku_1', 'sku_2'];
$collection = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Collection::class)
- ->setMethods(['getIterator', 'addFieldToFilter'])
+ ->setMethods(
+ [
+ 'getItems',
+ 'addFieldToFilter',
+ 'setPageSize',
+ 'getLastPageNumber',
+ 'setCurPage',
+ 'clear'
+ ]
+ )
->disableOriginalConstructor()->getMock();
$product = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
->setMethods(['getSku', 'getData', 'getTypeId'])
@@ -69,7 +78,11 @@ public function testRetrieveProductIdsBySkus()
$this->collectionFactory->expects($this->once())->method('create')->willReturn($collection);
$collection->expects($this->once())->method('addFieldToFilter')
->with(\Magento\Catalog\Api\Data\ProductInterface::SKU, ['in' => $skus])->willReturnSelf();
- $collection->expects($this->once())->method('getIterator')->willReturn(new \ArrayIterator([$product]));
+ $collection->expects($this->atLeastOnce())->method('getItems')->willReturn([$product]);
+ $collection->expects($this->atLeastOnce())->method('setPageSize')->willReturnSelf();
+ $collection->expects($this->atLeastOnce())->method('getLastPageNumber')->willReturn(1);
+ $collection->expects($this->atLeastOnce())->method('setCurPage')->with(1)->willReturnSelf();
+ $collection->expects($this->atLeastOnce())->method('clear')->willReturnSelf();
$this->metadataPool
->expects($this->once())
->method('getMetadata')
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
index 034b04b6a757..cfb54c3aefd0 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
@@ -29,12 +29,12 @@
],
],
'renderer_attribute_with_invalid_value' => [
- '
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php
index 9e2b19660299..5a1a5906ec4b 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CategoryLinkTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product;
use Magento\Catalog\Model\ResourceModel\Product\CategoryLink;
@@ -129,9 +130,20 @@ public function testSaveCategoryLinks($newCategoryLinks, $dbCategoryLinks, $affe
);
}
+ $expectedResult = [];
+
+ foreach ($affectedIds as $type => $ids) {
+ $expectedResult = array_merge($expectedResult, $ids);
+ // Verify if the correct insert, update and/or delete actions are performed:
+ $this->setupExpectationsForConnection($type, $ids);
+ }
+
$actualResult = $this->model->saveCategoryLinks($product, $newCategoryLinks);
+
sort($actualResult);
- $this->assertEquals($affectedIds, $actualResult);
+ sort($expectedResult);
+
+ $this->assertEquals($expectedResult, $actualResult);
}
/**
@@ -151,7 +163,11 @@ public function getCategoryLinksDataProvider()
['category_id' => 3, 'position' => 10],
['category_id' => 4, 'position' => 20],
],
- [], // Nothing to update - data not changed
+ [
+ 'update' => [],
+ 'insert' => [],
+ 'delete' => [],
+ ],
],
[
[
@@ -162,7 +178,11 @@ public function getCategoryLinksDataProvider()
['category_id' => 3, 'position' => 10],
['category_id' => 4, 'position' => 20],
],
- [3, 4, 5], // 4 - updated position, 5 - added, 3 - deleted
+ [
+ 'update' => [4],
+ 'insert' => [5],
+ 'delete' => [3],
+ ],
],
[
[
@@ -173,7 +193,11 @@ public function getCategoryLinksDataProvider()
['category_id' => 3, 'position' => 10],
['category_id' => 4, 'position' => 20],
],
- [3, 4], // 3 - updated position, 4 - deleted
+ [
+ 'update' => [3],
+ 'insert' => [],
+ 'delete' => [4],
+ ],
],
[
[],
@@ -181,8 +205,80 @@ public function getCategoryLinksDataProvider()
['category_id' => 3, 'position' => 10],
['category_id' => 4, 'position' => 20],
],
- [3, 4], // 3, 4 - deleted
+ [
+ 'update' => [],
+ 'insert' => [],
+ 'delete' => [3, 4],
+ ],
],
+ [
+ [
+ ['category_id' => 3, 'position' => 10],
+ ['category_id' => 4, 'position' => 20],
+ ],
+ [
+ ['category_id' => 3, 'position' => 20], // swapped positions
+ ['category_id' => 4, 'position' => 10], // swapped positions
+ ],
+ [
+ 'update' => [3, 4],
+ 'insert' => [],
+ 'delete' => [],
+ ],
+ ]
];
}
+
+ /**
+ * @param $type
+ * @param $ids
+ */
+ private function setupExpectationsForConnection($type, $ids): void
+ {
+ switch ($type) {
+ case 'insert':
+ $this->connectionMock
+ ->expects($this->exactly(empty($ids) ? 0 : 1))
+ ->method('insertArray')
+ ->with(
+ $this->anything(),
+ $this->anything(),
+ $this->callback(function ($data) use ($ids) {
+ $foundIds = [];
+ foreach ($data as $row) {
+ $foundIds[] = $row['category_id'];
+ }
+ return $ids === $foundIds;
+ })
+ );
+ break;
+ case 'update':
+ $this->connectionMock
+ ->expects($this->exactly(empty($ids) ? 0 : 1))
+ ->method('insertOnDuplicate')
+ ->with(
+ $this->anything(),
+ $this->callback(function ($data) use ($ids) {
+ $foundIds = [];
+ foreach ($data as $row) {
+ $foundIds[] = $row['category_id'];
+ }
+ return $ids === $foundIds;
+ })
+ );
+ break;
+ case 'delete':
+ $this->connectionMock
+ ->expects($this->exactly(empty($ids) ? 0 : 1))
+ ->method('delete')
+ // Verify that the correct category ID's are touched:
+ ->with(
+ $this->anything(),
+ $this->callback(function ($data) use ($ids) {
+ return array_values($data)[1] === $ids;
+ })
+ );
+ break;
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
index dbbb3fb29513..0316b2e374d2 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
@@ -58,13 +58,18 @@ class CollectionTest extends \PHPUnit\Framework\TestCase
*/
private $storeManager;
+ /**
+ * @var \Magento\Framework\Data\Collection\EntityFactory|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $entityFactory;
+
/**
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
protected function setUp()
{
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
- $entityFactory = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class);
+ $this->entityFactory = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class);
$logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
@@ -120,32 +125,25 @@ protected function setUp()
$groupManagement = $this->getMockBuilder(\Magento\Customer\Api\GroupManagementInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
-
$this->connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)
->setMethods(['getId'])
->disableOriginalConstructor()
->getMockForAbstractClass();
-
$this->selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
->disableOriginalConstructor()
->getMock();
-
$this->entityMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class)
->disableOriginalConstructor()
->getMock();
-
$this->galleryResourceMock = $this->getMockBuilder(
\Magento\Catalog\Model\ResourceModel\Product\Gallery::class
)->disableOriginalConstructor()->getMock();
-
$this->metadataPoolMock = $this->getMockBuilder(
\Magento\Framework\EntityManager\MetadataPool::class
)->disableOriginalConstructor()->getMock();
-
$this->galleryReadHandlerMock = $this->getMockBuilder(
\Magento\Catalog\Model\Product\Gallery\ReadHandler::class
)->disableOriginalConstructor()->getMock();
-
$this->storeManager->expects($this->any())->method('getId')->willReturn(1);
$this->storeManager->expects($this->any())->method('getStore')->willReturnSelf();
$universalFactory->expects($this->exactly(1))->method('create')->willReturnOnConsecutiveCalls(
@@ -168,7 +166,7 @@ protected function setUp()
$this->collection = $this->objectManager->getObject(
\Magento\Catalog\Model\ResourceModel\Product\Collection::class,
[
- 'entityFactory' => $entityFactory,
+ 'entityFactory' => $this->entityFactory,
'logger' => $logger,
'fetchStrategy' => $fetchStrategy,
'eventManager' => $eventManager,
@@ -318,7 +316,7 @@ public function testAddTierPriceDataByGroupId()
[ '(customer_group_id=? AND all_groups=0) OR all_groups=1', $customerGroupId]
)
->willReturnSelf();
- $select->expects($this->once())->method('order')->with('entity_id')->willReturnSelf();
+ $select->expects($this->once())->method('order')->with('qty')->willReturnSelf();
$this->connectionMock->expects($this->once())
->method('fetchAll')
->with($select)
@@ -370,7 +368,7 @@ public function testAddTierPriceData()
$select->expects($this->exactly(1))->method('where')
->with('entity_id IN(?)', [1])
->willReturnSelf();
- $select->expects($this->once())->method('order')->with('entity_id')->willReturnSelf();
+ $select->expects($this->once())->method('order')->with('qty')->willReturnSelf();
$this->connectionMock->expects($this->once())
->method('fetchAll')
->with($select)
@@ -379,4 +377,20 @@ public function testAddTierPriceData()
$this->assertSame($this->collection, $this->collection->addTierPriceData());
}
+
+ /**
+ * Test for getNewEmptyItem() method
+ *
+ * @return void
+ */
+ public function testGetNewEmptyItem()
+ {
+ $item = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->entityFactory->expects($this->once())->method('create')->willReturn($item);
+ $firstItem = $this->collection->getNewEmptyItem();
+ $secondItem = $this->collection->getNewEmptyItem();
+ $this->assertEquals($firstItem, $secondItem);
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php
index dfed4e4f3738..47ef3c999125 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 000000000000..4fce12dc2de8
--- /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 1c4764433814..728044b89caf 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/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php
index a21883eb4a18..ee487041600b 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php
@@ -16,7 +16,7 @@
use Magento\Framework\EntityManager\EntityMetadataInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use Magento\Store\Api\StoreResolverInterface;
+use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Model\Store;
/**
@@ -35,9 +35,9 @@ class StatusBaseSelectProcessorTest extends \PHPUnit\Framework\TestCase
private $metadataPool;
/**
- * @var StoreResolverInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- private $storeResolver;
+ private $storeManager;
/**
* @var Select|\PHPUnit_Framework_MockObject_MockObject
@@ -53,13 +53,13 @@ protected function setUp()
{
$this->eavConfig = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock();
$this->metadataPool = $this->getMockBuilder(MetadataPool::class)->disableOriginalConstructor()->getMock();
- $this->storeResolver = $this->getMockBuilder(StoreResolverInterface::class)->getMock();
+ $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)->getMock();
$this->select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock();
$this->statusBaseSelectProcessor = (new ObjectManager($this))->getObject(StatusBaseSelectProcessor::class, [
'eavConfig' => $this->eavConfig,
'metadataPool' => $this->metadataPool,
- 'storeResolver' => $this->storeResolver,
+ 'storeManager' => $this->storeManager,
]);
}
@@ -94,8 +94,14 @@ public function testProcess()
->with(Product::ENTITY, ProductInterface::STATUS)
->willReturn($statusAttribute);
- $this->storeResolver->expects($this->once())
- ->method('getCurrentStoreId')
+ $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)->getMock();
+
+ $this->storeManager->expects($this->once())
+ ->method('getStore')
+ ->willReturn($storeMock);
+
+ $storeMock->expects($this->once())
+ ->method('getId')
->willReturn($currentStoreId);
$this->select->expects($this->at(0))
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php b/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php
index 37b0e15cac65..e225ec0daef6 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/_files/converted_view.php
@@ -11,7 +11,41 @@
"type" => "swatch_thumb",
"width" => 75,
"height" => 75,
- "background" => [255, 25, 2]
+ "constrain" => false,
+ "aspect_ratio" => false,
+ "frame" => false,
+ "transparency" => false,
+ "background" => [255, 25, 2],
+ ],
+ "swatch_thumb_medium" => [
+ "type" => "swatch_medium",
+ "width" => 750,
+ "height" => 750,
+ "constrain" => true,
+ "aspect_ratio" => true,
+ "frame" => true,
+ "transparency" => true,
+ "background" => [255, 25, 2],
+ ],
+ "swatch_thumb_large" => [
+ "type" => "swatch_large",
+ "width" => 1080,
+ "height" => 720,
+ "constrain" => false,
+ "aspect_ratio" => false,
+ "frame" => false,
+ "transparency" => false,
+ "background" => [255, 25, 2],
+ ],
+ "swatch_thumb_small" => [
+ "type" => "swatch_small",
+ "width" => 100,
+ "height" => 100,
+ "constrain" => true,
+ "aspect_ratio" => true,
+ "frame" => true,
+ "transparency" => true,
+ "background" => [255, 25, 2],
]
]
]
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/_files/valid_view.xml b/app/code/Magento/Catalog/Test/Unit/Model/_files/valid_view.xml
index 253abc5e2e48..ee4ddaad5342 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/_files/valid_view.xml
+++ b/app/code/Magento/Catalog/Test/Unit/Model/_files/valid_view.xml
@@ -11,6 +11,37 @@
75
75
+ false
+ false
+ false
+ false
+ [255, 25, 2]
+
+
+ 750
+ 750
+ true
+ true
+ true
+ true
+ [255, 25, 2]
+
+
+ 1080
+ 720
+ 0
+ 0
+ 0
+ 0
+ [255, 25, 2]
+
+
+ 100
+ 100
+ 1
+ 1
+ 1
+ 1
[255, 25, 2]
diff --git a/app/code/Magento/Catalog/Test/Unit/Plugin/Model/Attribute/Backend/AttributeValidationTest.php b/app/code/Magento/Catalog/Test/Unit/Plugin/Model/Attribute/Backend/AttributeValidationTest.php
new file mode 100644
index 000000000000..463ecf881977
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Plugin/Model/Attribute/Backend/AttributeValidationTest.php
@@ -0,0 +1,154 @@
+attributeMock = $this->getMockBuilder(AbstractBackend::class)
+ ->setMethods(['getAttributeCode'])
+ ->getMockForAbstractClass();
+ $this->subjectMock = $this->getMockBuilder(AbstractBackend::class)
+ ->setMethods(['getAttribute'])
+ ->getMockForAbstractClass();
+ $this->subjectMock->expects($this->any())
+ ->method('getAttribute')
+ ->willReturn($this->attributeMock);
+
+ $this->storeMock = $this->getMockBuilder(StoreInterface::class)
+ ->setMethods(['getId'])
+ ->getMockForAbstractClass();
+ $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class)
+ ->setMethods(['getStore'])
+ ->getMockForAbstractClass();
+ $this->storeManagerMock->expects($this->any())
+ ->method('getStore')
+ ->willReturn($this->storeMock);
+
+ $this->entityMock = $this->getMockBuilder(DataObject::class)
+ ->setMethods(['getData'])
+ ->getMock();
+
+ $this->allowedEntityTypes = [$this->entityMock];
+
+ $this->proceedMock = function () {
+ $this->isProceedMockCalled = true;
+ };
+
+ $this->attributeValidation = $objectManager->getObject(
+ AttributeValidation::class,
+ [
+ 'storeManager' => $this->storeManagerMock,
+ 'allowedEntityTypes' => $this->allowedEntityTypes,
+ ]
+ );
+ }
+
+ /**
+ * @param bool $shouldProceedRun
+ * @param bool $defaultStoreUsed
+ * @param null|int|string $storeId
+ * @dataProvider aroundValidateDataProvider
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @return void
+ */
+ public function testAroundValidate(bool $shouldProceedRun, bool $defaultStoreUsed, $storeId)
+ {
+ $this->isProceedMockCalled = false;
+ $attributeCode = 'code';
+
+ $this->storeMock->expects($this->once())
+ ->method('getId')
+ ->willReturn($storeId);
+ if ($defaultStoreUsed) {
+ $this->attributeMock->expects($this->once())
+ ->method('getAttributeCode')
+ ->willReturn($attributeCode);
+ $this->entityMock->expects($this->at(0))
+ ->method('getData')
+ ->willReturn([$attributeCode => null]);
+ $this->entityMock->expects($this->at(1))
+ ->method('getData')
+ ->with($attributeCode)
+ ->willReturn(null);
+ }
+
+ $this->attributeValidation->aroundValidate($this->subjectMock, $this->proceedMock, $this->entityMock);
+ $this->assertSame($shouldProceedRun, $this->isProceedMockCalled);
+ }
+
+ /**
+ * Data provider for testAroundValidate
+ * @return array
+ */
+ public function aroundValidateDataProvider(): array
+ {
+ return [
+ [true, false, '0'],
+ [true, false, 0],
+ [true, false, null],
+ [false, true, 1],
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php
new file mode 100644
index 000000000000..774edcfeb6b6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php
@@ -0,0 +1,156 @@
+objectManager = new ObjectManager($this);
+
+ $this->attribute = $this->getMockBuilder(ProductAttributeInterface::class)
+ ->setMethods(['usesSource'])
+ ->getMockForAbstractClass();
+ $this->context = $this->createMock(ContextInterface::class);
+ $this->uiComponentFactory = $this->createMock(UiComponentFactory::class);
+ $this->column = $this->getMockForAbstractClass(ColumnInterface::class);
+ $this->uiComponentFactory->method('create')
+ ->willReturn($this->column);
+
+ $this->columnFactory = $this->objectManager->getObject(ColumnFactory::class, [
+ 'componentFactory' => $this->uiComponentFactory
+ ]);
+ }
+
+ /**
+ * Tests the create method will return correct object.
+ *
+ * @return void
+ */
+ public function testCreatedObject(): void
+ {
+ $this->context->method('getRequestParam')
+ ->with(FilterModifier::FILTER_MODIFIER, [])
+ ->willReturn([]);
+
+ $object = $this->columnFactory->create($this->attribute, $this->context);
+ $this->assertEquals(
+ $this->column,
+ $object,
+ 'Object must be the same which the ui component factory creates.'
+ );
+ }
+
+ /**
+ * Tests create method with not filterable in grid attribute.
+ *
+ * @param array $filterModifiers
+ * @param null|string $filter
+ *
+ * @return void
+ * @dataProvider filterModifiersProvider
+ */
+ public function testCreateWithNotFilterableInGridAttribute(array $filterModifiers, ?string $filter): void
+ {
+ $componentFactoryArgument = [
+ 'data' => [
+ 'config' => [
+ 'label' => __(null),
+ 'dataType' => 'text',
+ 'add_field' => true,
+ 'visible' => null,
+ 'filter' => $filter,
+ 'component' => 'Magento_Ui/js/grid/columns/column',
+ ],
+ ],
+ 'context' => $this->context,
+ ];
+
+ $this->context->method('getRequestParam')
+ ->with(FilterModifier::FILTER_MODIFIER, [])
+ ->willReturn($filterModifiers);
+ $this->attribute->method('getIsFilterableInGrid')
+ ->willReturn(false);
+ $this->attribute->method('getAttributeCode')
+ ->willReturn('color');
+
+ $this->uiComponentFactory->expects($this->once())
+ ->method('create')
+ ->with($this->anything(), $this->anything(), $componentFactoryArgument);
+
+ $this->columnFactory->create($this->attribute, $this->context);
+ }
+
+ /**
+ * Filter modifiers data provider.
+ *
+ * @return array
+ */
+ public function filterModifiersProvider(): array
+ {
+ return [
+ 'without' => [
+ 'filter_modifiers' => [],
+ 'filter' => null,
+ ],
+ 'with' => [
+ 'filter_modifiers' => [
+ 'color' => [
+ 'condition_type' => 'notnull',
+ ],
+ ],
+ 'filter' => 'text',
+ ],
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php
index cd6565f32ed1..a2d81854607a 100644
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/CategoriesTest.php
@@ -154,38 +154,4 @@ public function modifyMetaLockedDataProvider()
{
return [[true], [false]];
}
-
- public function testModifyMetaWithCaching()
- {
- $this->arrayManagerMock->expects($this->exactly(2))
- ->method('findPath')
- ->willReturn(true);
- $cacheManager = $this->getMockBuilder(CacheInterface::class)
- ->getMockForAbstractClass();
- $cacheManager->expects($this->once())
- ->method('load')
- ->with(Categories::CATEGORY_TREE_ID . '_');
- $cacheManager->expects($this->once())
- ->method('save');
-
- $modifier = $this->createModel();
- $cacheContextProperty = new \ReflectionProperty(
- Categories::class,
- 'cacheManager'
- );
- $cacheContextProperty->setAccessible(true);
- $cacheContextProperty->setValue($modifier, $cacheManager);
-
- $groupCode = 'test_group_code';
- $meta = [
- $groupCode => [
- 'children' => [
- 'category_ids' => [
- 'sortOrder' => 10,
- ],
- ],
- ],
- ];
- $modifier->modifyMeta($meta);
- }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php
index c3096770729a..829dc4824416 100644
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Form/Modifier/WebsitesTest.php
@@ -74,6 +74,9 @@ class WebsitesTest extends AbstractModifierTest
*/
protected $storeViewMock;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
parent::setUp();
@@ -90,14 +93,11 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
$this->websiteRepositoryMock = $this->getMockBuilder(\Magento\Store\Api\WebsiteRepositoryInterface::class)
- ->setMethods(['getList', 'getDefault'])
+ ->setMethods(['getList'])
->getMockForAbstractClass();
$this->websiteRepositoryMock->expects($this->any())
->method('getDefault')
->willReturn($this->websiteMock);
- $this->websiteRepositoryMock->expects($this->any())
- ->method('getList')
- ->willReturn([$this->websiteMock, $this->secondWebsiteMock]);
$this->groupRepositoryMock = $this->getMockBuilder(\Magento\Store\Api\GroupRepositoryInterface::class)
->setMethods(['getList'])
->getMockForAbstractClass();
@@ -111,8 +111,10 @@ protected function setUp()
->method('getWebsiteIds')
->willReturn($this->assignedWebsites);
$this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)
- ->setMethods(['isSingleStoreMode'])
+ ->setMethods(['isSingleStoreMode', 'getWesites'])
->getMockForAbstractClass();
+ $this->storeManagerMock->method('getWebsites')
+ ->willReturn([$this->websiteMock, $this->secondWebsiteMock]);
$this->storeManagerMock->expects($this->any())
->method('isSingleStoreMode')
->willReturn(false);
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php
index 12bc9acfa4c5..009cd690d4cd 100644
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/Listing/Collector/ImageTest.php
@@ -15,6 +15,7 @@
use Magento\Catalog\Helper\ImageFactory;
use Magento\Catalog\Api\Data\ProductRender\ImageInterface;
use Magento\Catalog\Helper\Image as ImageHelper;
+use Magento\Framework\View\DesignLoader;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -33,6 +34,9 @@ class ImageTest extends \PHPUnit\Framework\TestCase
/** @var DesignInterface | \PHPUnit_Framework_MockObject_MockObject */
private $design;
+ /** @var DesignLoader | \PHPUnit_Framework_MockObject_MockObject*/
+ private $designLoader;
+
/** @var Image */
private $model;
@@ -60,13 +64,15 @@ public function setUp()
->getMock();
$this->storeManager = $this->createMock(StoreManagerInterface::class);
$this->design = $this->createMock(DesignInterface::class);
+ $this->designLoader = $this->createMock(DesignLoader::class);
$this->model = new Image(
$this->imageFactory,
$this->state,
$this->storeManager,
$this->design,
$this->imageInterfaceFactory,
- $this->imageCodes
+ $this->imageCodes,
+ $this->designLoader
);
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php
index 6d7c8814bd47..0e0cb676cdf3 100644
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php
@@ -54,7 +54,16 @@ protected function setUp()
->getMockForAbstractClass();
$this->collectionMock = $this->getMockBuilder(AbstractCollection::class)
->disableOriginalConstructor()
- ->setMethods(['load', 'getSelect', 'getTable', 'getIterator', 'isLoaded', 'toArray', 'getSize'])
+ ->setMethods([
+ 'load',
+ 'getSelect',
+ 'getTable',
+ 'getIterator',
+ 'isLoaded',
+ 'toArray',
+ 'getSize',
+ 'setStoreId'
+ ])
->getMockForAbstractClass();
$this->dbSelectMock = $this->getMockBuilder(DbSelect::class)
->disableOriginalConstructor()
diff --git a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php
index dbf1292e5736..a4ccaffc8fb6 100644
--- a/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/ViewModel/Product/BreadcrumbsTest.php
@@ -152,21 +152,21 @@ public function productJsonEncodeDataProvider() : array
return [
[
$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test ™']]),
- '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test \u2122"}}',
+ '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test \u2122"}}',
],
[
$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test "']]),
- '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test ""}}',
+ '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test ""}}',
],
[
$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test x']]),
- '{"breadcrumbs":{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":'
+ '{"breadcrumbs":{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":'
. '"Test <b>x<\/b>"}}',
],
[
$this->getObjectManager()->getObject(Product::class, ['data' => ['name' => 'Test \'abc\'']]),
'{"breadcrumbs":'
- . '{"categoryUrlSuffix":"."html","userCategoryPathInUrl":0,"product":"Test 'abc'"}}'
+ . '{"categoryUrlSuffix":"."html","useCategoryPathInUrl":0,"product":"Test 'abc'"}}'
],
];
}
diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
index cbc67fee8a5a..ea6b1fd47a0a 100644
--- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
+++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
@@ -5,7 +5,11 @@
*/
namespace Magento\Catalog\Ui\Component;
+use Magento\Ui\Component\Filters\FilterModifier;
+
/**
+ * Column Factory
+ *
* @api
* @since 100.0.2
*/
@@ -47,20 +51,26 @@ public function __construct(\Magento\Framework\View\Element\UiComponentFactory $
}
/**
+ * Create Factory
+ *
* @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
* @param \Magento\Framework\View\Element\UiComponent\ContextInterface $context
* @param array $config
+ *
* @return \Magento\Ui\Component\Listing\Columns\ColumnInterface
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function create($attribute, $context, array $config = [])
{
+ $filterModifiers = $context->getRequestParam(FilterModifier::FILTER_MODIFIER, []);
+
$columnName = $attribute->getAttributeCode();
$config = array_merge([
'label' => __($attribute->getDefaultFrontendLabel()),
'dataType' => $this->getDataType($attribute),
'add_field' => true,
'visible' => $attribute->getIsVisibleInGrid(),
- 'filter' => ($attribute->getIsFilterableInGrid())
+ 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers))
? $this->getFilterType($attribute->getFrontendInput())
: null,
], $config);
@@ -82,7 +92,10 @@ public function create($attribute, $context, array $config = [])
}
/**
+ * Get Js Component
+ *
* @param string $dataType
+ *
* @return string
*/
protected function getJsComponent($dataType)
@@ -91,14 +104,15 @@ protected function getJsComponent($dataType)
}
/**
+ * Get Data Type
+ *
* @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
+ *
* @return string
*/
protected function getDataType($attribute)
{
- return isset($this->dataTypeMap[$attribute->getFrontendInput()])
- ? $this->dataTypeMap[$attribute->getFrontendInput()]
- : $this->dataTypeMap['default'];
+ return $this->dataTypeMap[$attribute->getFrontendInput()] ?? $this->dataTypeMap['default'];
}
/**
@@ -111,6 +125,6 @@ protected function getFilterType($frontendInput)
{
$filtersMap = ['date' => 'dateRange'];
$result = array_replace_recursive($this->dataTypeMap, $filtersMap);
- return isset($result[$frontendInput]) ? $result[$frontendInput] : $result['default'];
+ return $result[$frontendInput] ?? $result['default'];
}
}
diff --git a/app/code/Magento/Catalog/Ui/Component/FilterFactory.php b/app/code/Magento/Catalog/Ui/Component/FilterFactory.php
index fcc500c89160..dd8eaffb0a65 100644
--- a/app/code/Magento/Catalog/Ui/Component/FilterFactory.php
+++ b/app/code/Magento/Catalog/Ui/Component/FilterFactory.php
@@ -71,8 +71,6 @@ public function create($attribute, $context, $config = [])
*/
protected function getFilterType($attribute)
{
- return isset($this->filterMap[$attribute->getFrontendInput()])
- ? $this->filterMap[$attribute->getFrontendInput()]
- : $this->filterMap['default'];
+ return $this->filterMap[$attribute->getFrontendInput()] ?? $this->filterMap['default'];
}
}
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php
index c96498b054d2..8ea6d8b9e5a0 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php
@@ -80,6 +80,6 @@ public function prepare()
*/
protected function getFilterType($frontendInput)
{
- return isset($this->filterMap[$frontendInput]) ? $this->filterMap[$frontendInput] : $this->filterMap['default'];
+ return $this->filterMap[$frontendInput] ?? $this->filterMap['default'];
}
}
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
index d4dc9ddd7ca3..09c9782fc0e3 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
@@ -9,6 +9,8 @@
use Magento\Framework\View\Element\UiComponent\ContextInterface;
/**
+ * Class Thumbnail
+ *
* @api
* @since 100.0.2
*/
@@ -67,6 +69,8 @@ public function prepareDataSource(array $dataSource)
}
/**
+ * Get Alt
+ *
* @param array $row
*
* @return null|string
@@ -74,6 +78,6 @@ public function prepareDataSource(array $dataSource)
protected function getAlt($row)
{
$altField = $this->getData('config/altField') ?: self::ALT_FIELD;
- return isset($row[$altField]) ? $row[$altField] : null;
+ return $row[$altField] ?? null;
}
}
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
index 5af0d71dc246..494b77724e5b 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
@@ -3,13 +3,19 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Ui\Component\Listing\Columns;
-use Magento\Framework\View\Element\UiComponentFactory;
+use Magento\Framework\DB\Helper;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
+use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Store\Model\StoreManagerInterface;
/**
+ * Websites listing column component.
+ *
* @api
* @since 100.0.2
*/
@@ -20,6 +26,11 @@ class Websites extends \Magento\Ui\Component\Listing\Columns\Column
*/
const NAME = 'websites';
+ /**
+ * Data for concatenated website names value.
+ */
+ private $websiteNames = 'website_names';
+
/**
* Store manager
*
@@ -27,26 +38,36 @@ class Websites extends \Magento\Ui\Component\Listing\Columns\Column
*/
protected $storeManager;
+ /**
+ * @var \Magento\Framework\DB\Helper
+ */
+ private $resourceHelper;
+
/**
* @param ContextInterface $context
* @param UiComponentFactory $uiComponentFactory
* @param StoreManagerInterface $storeManager
* @param array $components
* @param array $data
+ * @param Helper $resourceHelper
*/
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
StoreManagerInterface $storeManager,
array $components = [],
- array $data = []
+ array $data = [],
+ Helper $resourceHelper = null
) {
parent::__construct($context, $uiComponentFactory, $components, $data);
+ $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$this->storeManager = $storeManager;
+ $this->resourceHelper = $resourceHelper ?: $objectManager->get(Helper::class);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @deprecated 101.0.0
*/
public function prepareDataSource(array $dataSource)
@@ -71,9 +92,10 @@ public function prepareDataSource(array $dataSource)
return $dataSource;
}
-
+
/**
- * Prepare component configuration
+ * Prepare component configuration.
+ *
* @return void
*/
public function prepare()
@@ -83,4 +105,46 @@ public function prepare()
$this->_data['config']['componentDisabled'] = true;
}
}
+
+ /**
+ * Apply sorting.
+ *
+ * @return void
+ */
+ protected function applySorting()
+ {
+ $sorting = $this->getContext()->getRequestParam('sorting');
+ $isSortable = $this->getData('config/sortable');
+ if ($isSortable !== false
+ && !empty($sorting['field'])
+ && !empty($sorting['direction'])
+ && $sorting['field'] === $this->getName()
+ ) {
+ $collection = $this->getContext()->getDataProvider()->getCollection();
+ $collection
+ ->joinField(
+ 'websites_ids',
+ 'catalog_product_website',
+ 'website_id',
+ 'product_id=entity_id',
+ null,
+ 'left'
+ )
+ ->joinTable(
+ 'store_website',
+ 'website_id = websites_ids',
+ ['name'],
+ null,
+ 'left'
+ )
+ ->groupByAttribute('entity_id');
+ $this->resourceHelper->addGroupConcatColumn(
+ $collection->getSelect(),
+ $this->websiteNames,
+ 'name'
+ );
+
+ $collection->getSelect()->order($this->websiteNames . ' ' . $sorting['direction']);
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
index 336aeffa1058..00132c6ad89e 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
@@ -139,7 +139,8 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
public function modifyMeta(array $meta)
@@ -158,7 +159,8 @@ public function modifyMeta(array $meta)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @since 101.0.0
*/
public function modifyData(array $data)
@@ -381,11 +383,15 @@ private function addAdvancedPriceLink()
);
$advancedPricingButton['arguments']['data']['config'] = [
+ 'dataScope' => 'advanced_pricing_button',
'displayAsLink' => true,
'formElement' => Container::NAME,
'componentType' => Container::NAME,
'component' => 'Magento_Ui/js/form/components/button',
'template' => 'ui/form/components/button/container',
+ 'imports' => [
+ 'childError' => $this->scopeName . '.advanced_pricing_modal.advanced-pricing:error',
+ ],
'actions' => [
[
'targetName' => $this->scopeName . '.advanced_pricing_modal',
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php
index ed737df708ab..800ead0e4030 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Ui\DataProvider\Product\Form\Modifier;
use Magento\Catalog\Model\Locator\LocatorInterface;
@@ -11,6 +13,7 @@
use Magento\Framework\App\CacheInterface;
use Magento\Framework\DB\Helper as DbHelper;
use Magento\Catalog\Model\Category as CategoryModel;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Framework\UrlInterface;
use Magento\Framework\Stdlib\ArrayManager;
@@ -118,7 +121,7 @@ private function getCacheManager()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.0.0
*/
public function modifyMeta(array $meta)
@@ -130,7 +133,7 @@ public function modifyMeta(array $meta)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.0.0
*/
public function modifyData(array $data)
@@ -202,6 +205,7 @@ protected function createNewCategoryModal(array $meta)
*
* @param array $meta
* @return array
+ * @throws LocalizedException
* @since 101.0.0
*/
protected function customizeCategoriesField(array $meta)
@@ -289,6 +293,7 @@ protected function customizeCategoriesField(array $meta)
'source' => 'product_details',
'displayArea' => 'insideGroup',
'sortOrder' => 20,
+ 'dataScope' => $fieldCode,
],
],
]
@@ -305,20 +310,64 @@ protected function customizeCategoriesField(array $meta)
*
* @param string|null $filter
* @return array
+ * @throws LocalizedException
* @since 101.0.0
*/
protected function getCategoriesTree($filter = null)
{
- $categoryTree = $this->getCacheManager()->load(self::CATEGORY_TREE_ID . '_' . $filter);
- if ($categoryTree) {
- return $this->serializer->unserialize($categoryTree);
+ $storeId = (int) $this->locator->getStore()->getId();
+
+ $cachedCategoriesTree = $this->getCacheManager()
+ ->load($this->getCategoriesTreeCacheId($storeId, (string) $filter));
+ if (!empty($cachedCategoriesTree)) {
+ return $this->serializer->unserialize($cachedCategoriesTree);
}
- $storeId = $this->locator->getStore()->getId();
+ $categoriesTree = $this->retrieveCategoriesTree(
+ $storeId,
+ $this->retrieveShownCategoriesIds($storeId, (string) $filter)
+ );
+
+ $this->getCacheManager()->save(
+ $this->serializer->serialize($categoriesTree),
+ $this->getCategoriesTreeCacheId($storeId, (string) $filter),
+ [
+ \Magento\Catalog\Model\Category::CACHE_TAG,
+ \Magento\Framework\App\Cache\Type\Block::CACHE_TAG
+ ]
+ );
+
+ return $categoriesTree;
+ }
+
+ /**
+ * Get cache id for categories tree.
+ *
+ * @param int $storeId
+ * @param string $filter
+ * @return string
+ */
+ private function getCategoriesTreeCacheId(int $storeId, string $filter = '') : string
+ {
+ return self::CATEGORY_TREE_ID
+ . '_' . (string) $storeId
+ . '_' . $filter;
+ }
+
+ /**
+ * Retrieve filtered list of categories id.
+ *
+ * @param int $storeId
+ * @param string $filter
+ * @return array
+ * @throws LocalizedException
+ */
+ private function retrieveShownCategoriesIds(int $storeId, string $filter = '') : array
+ {
/* @var $matchingNamesCollection \Magento\Catalog\Model\ResourceModel\Category\Collection */
$matchingNamesCollection = $this->categoryCollectionFactory->create();
- if ($filter !== null) {
+ if (!empty($filter)) {
$matchingNamesCollection->addAttributeToFilter(
'name',
['like' => $this->dbHelper->addLikeEscape($filter, ['position' => 'any'])]
@@ -338,6 +387,19 @@ protected function getCategoriesTree($filter = null)
}
}
+ return $shownCategoriesIds;
+ }
+
+ /**
+ * Retrieve tree of categories with attributes.
+ *
+ * @param int $storeId
+ * @param array $shownCategoriesIds
+ * @return array|null
+ * @throws LocalizedException
+ */
+ private function retrieveCategoriesTree(int $storeId, array $shownCategoriesIds) : ?array
+ {
/* @var $collection \Magento\Catalog\Model\ResourceModel\Category\Collection */
$collection = $this->categoryCollectionFactory->create();
@@ -364,15 +426,6 @@ protected function getCategoriesTree($filter = null)
$categoryById[$category->getParentId()]['optgroup'][] = &$categoryById[$category->getId()];
}
- $this->getCacheManager()->save(
- $this->serializer->serialize($categoryById[CategoryModel::TREE_ROOT_ID]['optgroup']),
- self::CATEGORY_TREE_ID . '_' . $filter,
- [
- \Magento\Catalog\Model\Category::CACHE_TAG,
- \Magento\Framework\App\Cache\Type\Block::CACHE_TAG
- ]
- );
-
return $categoryById[CategoryModel::TREE_ROOT_ID]['optgroup'];
}
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
index 86f1db2022cc..af43c84501f6 100755
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
@@ -11,6 +11,7 @@
use Magento\Catalog\Model\Config\Source\Product\Options\Price as ProductOptionsPrice;
use Magento\Framework\UrlInterface;
use Magento\Framework\Stdlib\ArrayManager;
+use Magento\Ui\Component\Form\Element\Hidden;
use Magento\Ui\Component\Modal;
use Magento\Ui\Component\Container;
use Magento\Ui\Component\DynamicRows;
@@ -867,7 +868,7 @@ protected function getPositionFieldConfig($sortOrder)
'data' => [
'config' => [
'componentType' => Field::NAME,
- 'formElement' => Input::NAME,
+ 'formElement' => Hidden::NAME,
'dataScope' => static::FIELD_SORT_ORDER_NAME,
'dataType' => Number::NAME,
'visible' => false,
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
old mode 100755
new mode 100644
index b60f1592633b..8326c3b53189
--- 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;
@@ -290,7 +291,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.0.0
*/
public function modifyMeta(array $meta)
@@ -401,7 +402,7 @@ public function getContainerChildren(ProductAttributeInterface $attribute, $grou
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.0.0
*/
public function modifyData(array $data)
@@ -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
*
@@ -532,8 +559,8 @@ private function getAttributes()
/**
* Loads attributes for specified groups at once
*
- * @param AttributeGroupInterface[] ...$groups
- * @return @return ProductAttributeInterface[]
+ * @param AttributeGroupInterface[] $groups
+ * @return ProductAttributeInterface[]
*/
private function loadAttributesForGroups(array $groups)
{
@@ -649,7 +676,7 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC
// TODO: Refactor to $attribute->getOptions() when MAGETWO-48289 is done
$attributeModel = $this->getAttributeModel($attribute);
if ($attributeModel->usesSource()) {
- $options = $attributeModel->getSource()->getAllOptions();
+ $options = $attributeModel->getSource()->getAllOptions(true, true);
$meta = $this->arrayManager->merge($configPath, $meta, [
'options' => $this->convertOptionsValueToString($options),
]);
@@ -707,7 +734,8 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC
}
/**
- * Returns attribute default value, based on db setting or setting in the system configuration
+ * Returns attribute default value, based on db setting or setting in the system configuration.
+ *
* @param ProductAttributeInterface $attribute
* @return null|string
*/
@@ -742,6 +770,8 @@ private function convertOptionsValueToString(array $options) : array
}
/**
+ * Adds 'use default value' checkbox.
+ *
* @param ProductAttributeInterface $attribute
* @param array $meta
* @return array
@@ -890,7 +920,7 @@ private function getFormElementsMapValue($value)
{
$valueMap = $this->formElementMapper->getMappings();
- return isset($valueMap[$value]) ? $valueMap[$value] : $value;
+ return $valueMap[$value] ?? $value;
}
/**
@@ -944,6 +974,9 @@ private function canDisplayUseDefault(ProductAttributeInterface $attribute)
$attributeCode = $attribute->getAttributeCode();
/** @var Product $product */
$product = $this->locator->getProduct();
+ if ($product->isLockedAttribute($attributeCode)) {
+ return false;
+ }
if (isset($this->canDisplayUseDefault[$attributeCode])) {
return $this->canDisplayUseDefault[$attributeCode];
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav/CompositeConfigProcessor.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav/CompositeConfigProcessor.php
index 5513af9d98e7..fed94193225f 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav/CompositeConfigProcessor.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav/CompositeConfigProcessor.php
@@ -10,6 +10,9 @@
use Psr\Log\LoggerInterface as Logger;
+/**
+ * Process config for Wysiwyg.
+ */
class CompositeConfigProcessor implements WysiwygConfigDataProcessorInterface
{
/**
@@ -24,6 +27,7 @@ class CompositeConfigProcessor implements WysiwygConfigDataProcessorInterface
/**
* CompositeConfigProcessor constructor.
+ * @param Logger $logger
* @param array $eavWysiwygDataProcessors
*/
public function __construct(Logger $logger, array $eavWysiwygDataProcessors)
@@ -33,7 +37,7 @@ public function __construct(Logger $logger, array $eavWysiwygDataProcessors)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function process(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute)
{
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 98de8ea34767..26044eb91a30 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)
{
@@ -349,8 +355,10 @@ protected function customizeNameListeners(array $meta)
'allowImport' => !$this->locator->getProduct()->getId(),
];
- if (!in_array($listener, $textListeners)) {
- $importsConfig['elementTmpl'] = 'ui/form/element/input';
+ if (in_array($listener, $textListeners)) {
+ $importsConfig['cols'] = 15;
+ $importsConfig['rows'] = 2;
+ $importsConfig['elementTmpl'] = 'ui/form/element/textarea';
}
$meta = $this->arrayManager->merge($listenerPath . static::META_CONFIG_PATH, $meta, $importsConfig);
@@ -361,7 +369,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 0eddca332220..a529580e2923 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/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
index bab36ce5fc4d..9cbbb86a2c55 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
@@ -89,7 +89,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.0.0
*/
public function modifyData(array $data)
@@ -117,7 +117,7 @@ public function modifyData(array $data)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.0.0
*/
public function modifyMeta(array $meta)
@@ -175,9 +175,11 @@ protected function getFieldsForFieldset()
$label = __('Websites');
$defaultWebsiteId = $this->websiteRepository->getDefault()->getId();
+ $isOnlyOneWebsiteAvailable = count($websitesList) === 1;
foreach ($websitesList as $website) {
$isChecked = in_array($website['id'], $websiteIds)
- || ($defaultWebsiteId == $website['id'] && $isNewProduct);
+ || ($defaultWebsiteId == $website['id'] && $isNewProduct)
+ || $isOnlyOneWebsiteAvailable;
$children[$website['id']] = [
'arguments' => [
'data' => [
@@ -331,6 +333,8 @@ protected function getWebsitesOptions()
}
/**
+ * Returns websites options list.
+ *
* @return array
* @since 101.0.0
*/
@@ -397,8 +401,9 @@ protected function getWebsitesList()
$this->websitesList = [];
$groupList = $this->groupRepository->getList();
$storesList = $this->storeRepository->getList();
+ $websiteList = $this->storeManager->getWebsites(true);
- foreach ($this->websiteRepository->getList() as $website) {
+ foreach ($websiteList as $website) {
$websiteId = $website->getId();
if (!$websiteId) {
continue;
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/Collector/Image.php
index 216bc16968fc..4fcb87ab1396 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
@@ -14,15 +14,18 @@
use Magento\Catalog\Model\Product\Image\NotLoadInfoImageException;
use Magento\Catalog\Ui\DataProvider\Product\ProductRenderCollectorInterface;
use Magento\Framework\App\State;
+use Magento\Framework\View\Design\ThemeInterface;
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 +54,7 @@ class Image implements ProductRenderCollectorInterface
/**
* @var DesignInterface
+ * @deprecated 2.3.0 DesignLoader is used for design theme loading
*/
private $design;
@@ -59,6 +63,11 @@ class Image implements ProductRenderCollectorInterface
*/
private $imageRenderInfoFactory;
+ /**
+ * @var DesignLoader
+ */
+ private $designLoader;
+
/**
* Image constructor.
* @param ImageFactory $imageFactory
@@ -67,6 +76,7 @@ class Image implements ProductRenderCollectorInterface
* @param DesignInterface $design
* @param ImageInterfaceFactory $imageRenderInfoFactory
* @param array $imageCodes
+ * @param DesignLoader $designLoader
*/
public function __construct(
ImageFactory $imageFactory,
@@ -74,7 +84,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 +93,8 @@ public function __construct(
$this->storeManager = $storeManager;
$this->design = $design;
$this->imageRenderInfoFactory = $imageRenderInfoFactory;
+ $this->designLoader = $designLoader ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(DesignLoader::class);
}
/**
@@ -92,6 +105,8 @@ public function __construct(
public function collect(ProductInterface $product, ProductRenderInterface $productRender)
{
$images = [];
+ /** @var ThemeInterface $currentTheme */
+ $currentTheme = $this->design->getDesignTheme();
foreach ($this->imageCodes as $imageCode) {
/** @var ImageInterface $image */
@@ -120,10 +135,13 @@ public function collect(ProductInterface $product, ProductRenderInterface $produ
$images[] = $image;
}
+ $this->design->setDesignTheme($currentTheme);
$productRender->setImages($images);
}
/**
+ * 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 +154,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/Ui/DataProvider/Product/Listing/DataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php
index 3090734df014..4de0b94d0680 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Listing/DataProvider.php
@@ -24,8 +24,6 @@ class DataProvider extends \Magento\Framework\View\Element\UiComponent\DataProvi
/**
* @param string $name
- * @param string $primaryFieldName
- * @param string $requestFieldName
* @param Reporting $reporting
* @param SearchCriteriaBuilder $searchCriteriaBuilder
* @param RequestInterface $request
@@ -61,7 +59,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getData()
{
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
index f4334bc25efd..e5451c8e4984 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
@@ -5,10 +5,16 @@
*/
namespace Magento\Catalog\Ui\DataProvider\Product;
+use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Eav\Model\Entity\Attribute\AttributeInterface;
+
/**
* Collection which is used for rendering product list in the backend.
*
* Used for product grid and customizes behavior of the default Product collection for grid needs.
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class ProductCollection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
{
@@ -25,4 +31,63 @@ protected function _productLimitationJoinPrice()
$this->_productLimitationFilters->setUsePriceIndex(false);
return $this->_productLimitationPrice(true);
}
+
+ /**
+ * Add attribute filter to collection
+ *
+ * @param AttributeInterface|integer|string|array $attribute
+ * @param null|string|array $condition
+ * @param string $joinType
+ * @return $this
+ * @throws LocalizedException
+ */
+ public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner')
+ {
+ $storeId = (int)$this->getStoreId();
+ if ($attribute === 'is_saleable'
+ || is_array($attribute)
+ || $storeId !== $this->getDefaultStoreId()
+ ) {
+ return parent::addAttributeToFilter($attribute, $condition, $joinType);
+ }
+
+ if ($attribute instanceof AttributeInterface) {
+ $attributeModel = $attribute;
+ } else {
+ $attributeModel = $this->getEntity()->getAttribute($attribute);
+ if ($attributeModel === false) {
+ throw new LocalizedException(
+ __('Invalid attribute identifier for filter (%1)', get_class($attribute))
+ );
+ }
+ }
+
+ if ($attributeModel->isScopeGlobal() || $attributeModel->getBackend()->isStatic()) {
+ return parent::addAttributeToFilter($attribute, $condition, $joinType);
+ }
+
+ $this->addAttributeToFilterAllStores($attributeModel, $condition);
+
+ return $this;
+ }
+
+ /**
+ * Add attribute to filter by all stores
+ *
+ * @param Attribute $attributeModel
+ * @param array $condition
+ * @return void
+ */
+ private function addAttributeToFilterAllStores(Attribute $attributeModel, array $condition): void
+ {
+ $tableName = $this->getTable($attributeModel->getBackendTable());
+ $entity = $this->getEntity();
+ $fKey = 'e.' . $this->getEntityPkName($entity);
+ $pKey = $tableName . '.' . $this->getEntityPkName($entity);
+ $condition = "({$pKey} = {$fKey}) AND ("
+ . $this->_getConditionSql("{$tableName}.value", $condition)
+ . ')';
+ $selectExistsInAllStores = $this->getConnection()->select()->from($tableName);
+ $this->getSelect()->exists($selectExistsInAllStores, $condition);
+ }
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php
index 200ecf89641f..a518afc576d6 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\App\ObjectManager;
+use Magento\Store\Model\Store;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use Magento\Ui\DataProvider\Modifier\PoolInterface;
@@ -67,6 +68,7 @@ public function __construct(
$this->addFieldStrategies = $addFieldStrategies;
$this->addFilterStrategies = $addFilterStrategies;
$this->modifiersPool = $modifiersPool ?: ObjectManager::getInstance()->get(PoolInterface::class);
+ $this->collection->setStoreId(Store::DEFAULT_STORE_ID);
}
/**
@@ -110,7 +112,7 @@ public function addField($field, $alias = null)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function addFilter(\Magento\Framework\Api\Filter $filter)
{
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorInterface.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorInterface.php
index 5d14cd21f7b9..3f16e0a6617d 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorInterface.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductRenderCollectorInterface.php
@@ -21,7 +21,6 @@ interface ProductRenderCollectorInterface
*
* @param ProductInterface $product
* @param ProductRenderInterface $productRender
- * @param array $data
* @return void
* @since 101.1.0
*/
diff --git a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php
index 95f2531e5fdc..d1424d637937 100644
--- a/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php
+++ b/app/code/Magento/Catalog/ViewModel/Product/Breadcrumbs.php
@@ -105,7 +105,7 @@ public function getJsonConfigurationHtmlEscaped() : string
[
'breadcrumbs' => [
'categoryUrlSuffix' => $this->escaper->escapeHtml($this->getCategoryUrlSuffix()),
- 'userCategoryPathInUrl' => (int)$this->isCategoryUsedInProductUrl(),
+ 'useCategoryPathInUrl' => (int)$this->isCategoryUsedInProductUrl(),
'product' => $this->escaper->escapeHtml($this->getProductName())
]
],
diff --git a/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php b/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php
new file mode 100644
index 000000000000..27829155af29
--- /dev/null
+++ b/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php
@@ -0,0 +1,58 @@
+stockConfiguration = $stockConfiguration;
+ }
+
+ /**
+ * Is product available for comparison.
+ *
+ * @param ProductInterface $product
+ * @return bool
+ */
+ public function isAvailableForCompare(ProductInterface $product): bool
+ {
+ return $this->isInStock($product) || $this->stockConfiguration->isShowOutOfStock();
+ }
+
+ /**
+ * Get is in stock status.
+ *
+ * @param ProductInterface $product
+ * @return bool
+ */
+ private function isInStock(ProductInterface $product): bool
+ {
+ $quantityAndStockStatus = $product->getQuantityAndStockStatus();
+ if (!$quantityAndStockStatus) {
+ return $product->isSalable();
+ }
+
+ return isset($quantityAndStockStatus['is_in_stock']) && $quantityAndStockStatus['is_in_stock'];
+ }
+}
diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json
index 44d051933909..5c3ee3da8ca8 100644
--- a/app/code/Magento/Catalog/composer.json
+++ b/app/code/Magento/Catalog/composer.json
@@ -7,6 +7,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
+ "magento/module-authorization": "*",
+ "magento/module-asynchronous-operations": "*",
"magento/module-backend": "*",
"magento/module-catalog-inventory": "*",
"magento/module-catalog-rule": "*",
diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml
index 10251d35dffc..c04cfb2dce00 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/di.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml
@@ -78,7 +78,7 @@
-
+
\Magento\Catalog\Ui\DataProvider\Product\ProductCollection
@@ -220,4 +220,16 @@
Magento\Catalog\Ui\DataProvider\Product\AddSearchKeyConditionToCollection
+
+
+
+ - category_ids
+ - gallery
+ - image
+ - media_gallery
+ - quantity_and_stock_status
+ - tier_price
+
+
+
diff --git a/app/code/Magento/Catalog/etc/adminhtml/events.xml b/app/code/Magento/Catalog/etc/adminhtml/events.xml
index f4fd7fc30398..ad83f5898237 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/events.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/events.xml
@@ -9,4 +9,7 @@
+
+
+
diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml
index 71a799fd2242..1d563244f143 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
@@ -56,7 +59,7 @@
Products per Page on Grid Allowed Values
Comma-separated.
- validate-per-page-value-list
+ validate-per-page-value-list required-entry
Products per Page on Grid Default Value
@@ -66,7 +69,7 @@
Products per Page on List Allowed Values
Comma-separated.
- validate-per-page-value-list
+ validate-per-page-value-list required-entry
Products per Page on List Default Value
@@ -83,8 +86,9 @@
Magento\Catalog\Model\Indexer\Product\Flat\System\Config\Mode
Magento\Config\Model\Config\Source\Yesno
-
+
Product Listing Sort by
+ Applies to category pages
Magento\Catalog\Model\Config\Source\ListSort
@@ -92,6 +96,11 @@
Whether to show "All" option in the "Show X Per Page" dropdown
Magento\Config\Model\Config\Source\Yesno
+
Product Image Placeholders
@@ -193,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/communication.xml b/app/code/Magento/Catalog/etc/communication.xml
new file mode 100644
index 000000000000..1a957f6ac9fe
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/communication.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml
index f52760aa5074..3a842166a382 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 7b4e1a96adda..17e3dddc41c3 100644
--- a/app/code/Magento/Catalog/etc/db_schema.xml
+++ b/app/code/Magento/Catalog/etc/db_schema.xml
@@ -9,7 +9,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
+ comment="Entity ID"/>
@@ -22,13 +22,13 @@
comment="Creation Time"/>
-
+
-
+
-
+
@@ -41,29 +41,29 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -76,30 +76,30 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
|
@@ -112,30 +112,30 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
|
@@ -148,29 +148,29 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -183,29 +183,29 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -218,40 +218,40 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
+ comment="Entity ID"/>
-
+
-
+
-
+
@@ -286,32 +286,32 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -324,33 +324,33 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -363,33 +363,33 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -402,32 +402,32 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -440,32 +440,32 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -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 @@
-
+
-
-
-
+
-
+
@@ -738,7 +738,7 @@
+ default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -788,13 +788,13 @@
comment="Media entry type"/>
-
+
-
-
+
@@ -805,7 +805,7 @@
+ default="0" comment="Entity ID"/>
@@ -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"/>
-
+
-
+
@@ -1651,15 +1656,16 @@
comment="Link Media value to Product entity table">
-
-
+
-
-
+
@@ -1676,20 +1682,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1705,20 +1711,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1742,18 +1748,18 @@
comment="Max Price"/>
-
+
-
+
-
+
-
+
@@ -1773,18 +1779,18 @@
default="0" comment="Store ID"/>
-
+
-
+
-
+
@@ -1805,21 +1811,21 @@
comment="Product Id"/>
-
+
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/etc/db_schema_whitelist.json b/app/code/Magento/Catalog/etc/db_schema_whitelist.json
index b1543a6a007f..d4bd6927d434 100644
--- a/app/code/Magento/Catalog/etc/db_schema_whitelist.json
+++ b/app/code/Magento/Catalog/etc/db_schema_whitelist.json
@@ -484,7 +484,8 @@
"index": {
"CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_STORE_ID": true,
"CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_ENTITY_ID": true,
- "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_VALUE_ID": true
+ "CATALOG_PRODUCT_ENTITY_MEDIA_GALLERY_VALUE_VALUE_ID": true,
+ "CAT_PRD_ENTT_MDA_GLR_VAL_ENTT_ID_VAL_ID_STORE_ID": true
},
"constraint": {
"PRIMARY": true,
@@ -1121,4 +1122,4 @@
"CATALOG_PRODUCT_FRONTEND_ACTION_CUSTOMER_ID_PRODUCT_ID_TYPE_ID": true
}
}
-}
\ No newline at end of file
+}
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index 32cc01f3036a..49447447622f 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -72,6 +72,7 @@
+
@@ -220,6 +221,12 @@
- gif
- png
+
+ - image/jpg
+ - image/jpeg
+ - image/gif
+ - image/png
+
@@ -596,6 +603,13 @@
+
+
+
+ - Magento\Catalog\Model\ProductOptionProcessor
+
+
+
@@ -657,12 +671,14 @@
- Magento\Catalog\Model\Product\Gallery\CreateHandler
- Magento\Catalog\Model\Category\Link\SaveHandler
- Magento\Catalog\Model\Product\Website\SaveHandler
+ - Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\SaveHandler
-
- Magento\Catalog\Model\Product\Option\SaveHandler
- Magento\Catalog\Model\Product\Gallery\UpdateHandler
- Magento\Catalog\Model\Category\Link\SaveHandler
- Magento\Catalog\Model\Product\Website\SaveHandler
+ - Magento\Catalog\Model\Product\Attribute\Backend\TierPrice\UpdateHandler
diff --git a/app/code/Magento/Catalog/etc/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml
index 55098037191e..ee9c5b29da89 100644
--- a/app/code/Magento/Catalog/etc/frontend/di.xml
+++ b/app/code/Magento/Catalog/etc/frontend/di.xml
@@ -116,4 +116,8 @@
+
+
+
diff --git a/app/code/Magento/Catalog/etc/product_options.xsd b/app/code/Magento/Catalog/etc/product_options.xsd
index 3bc24a909926..734c8f378d5d 100644
--- a/app/code/Magento/Catalog/etc/product_options.xsd
+++ b/app/code/Magento/Catalog/etc/product_options.xsd
@@ -61,11 +61,11 @@
- Model name can contain only [a-zA-Z_\\].
+ Model name can contain only ([\\]?[a-zA-Z_][a-zA-Z0-9_]*)+.
-
+
diff --git a/app/code/Magento/Catalog/etc/product_types_base.xsd b/app/code/Magento/Catalog/etc/product_types_base.xsd
index 6cc35fd7bee3..dec952bcf492 100644
--- a/app/code/Magento/Catalog/etc/product_types_base.xsd
+++ b/app/code/Magento/Catalog/etc/product_types_base.xsd
@@ -92,11 +92,11 @@
- Model name can contain only [a-zA-Z_\\].
+ Model name can contain only ([\\]?[a-zA-Z_][a-zA-Z0-9_]*)+.
-
+
diff --git a/app/code/Magento/Catalog/etc/queue.xml b/app/code/Magento/Catalog/etc/queue.xml
new file mode 100644
index 000000000000..137f34a5c1e2
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_consumer.xml b/app/code/Magento/Catalog/etc/queue_consumer.xml
new file mode 100644
index 000000000000..d9e66ae69c10
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_consumer.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_publisher.xml b/app/code/Magento/Catalog/etc/queue_publisher.xml
new file mode 100644
index 000000000000..1606ea42ec0b
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_publisher.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_topology.xml b/app/code/Magento/Catalog/etc/queue_topology.xml
new file mode 100644
index 000000000000..bdac891afbdb
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_topology.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml
index 2a5d60222e9f..44cdd473bf74 100644
--- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml
+++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml
index 2a5d60222e9f..44cdd473bf74 100644
--- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml
+++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv
index f2a3ab8f83f2..ed27dfd646cb 100644
--- a/app/code/Magento/Catalog/i18n/en_US.csv
+++ b/app/code/Magento/Catalog/i18n/en_US.csv
@@ -233,7 +233,6 @@ Products,Products
"This attribute set no longer exists.","This attribute set no longer exists."
"You saved the attribute set.","You saved the attribute set."
"Something went wrong while saving the attribute set.","Something went wrong while saving the attribute set."
-"You added product %1 to the comparison list.","You added product %1 to the comparison list."
"You cleared the comparison list.","You cleared the comparison list."
"Something went wrong clearing the comparison list.","Something went wrong clearing the comparison list."
"You removed product %1 from the comparison list.","You removed product %1 from the comparison list."
@@ -808,4 +807,5 @@ Details,Details
"Product Name or SKU", "Product Name or SKU"
"Start typing to find products", "Start typing to find products"
"Product with ID: (%1) doesn't exist", "Product with ID: (%1) doesn't exist"
-"Category with ID: (%1) doesn't exist", "Category with ID: (%1) doesn't exist"
\ No newline at end of file
+"Category with ID: (%1) doesn't exist", "Category with ID: (%1) doesn't exist"
+"You added product %1 to the comparison list.","You added product %1 to the comparison list."
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml
index 4a27158be5f7..d7aa058a1f44 100644
--- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssell.xml
@@ -5,6 +5,10 @@
* See COPYING.txt for license details.
*/
-->
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml
index b5efecf0d03c..3ba4562c9d3d 100644
--- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_crosssellgrid.xml
@@ -5,6 +5,10 @@
* See COPYING.txt for license details.
*/
-->
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml
index 1340d40ef4f9..c40c4a2818ef 100644
--- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_related.xml
@@ -5,6 +5,10 @@
* See COPYING.txt for license details.
*/
-->
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml
index 1ae83419ae64..38b791d88a00 100644
--- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_relatedgrid.xml
@@ -5,6 +5,10 @@
* See COPYING.txt for license details.
*/
-->
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml
index 44884897461a..4e7396608826 100644
--- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_set_block.xml
@@ -11,7 +11,7 @@
setGrid
- Magento\Eav\Model\ResourceModel\Entity\Attribute\Grid\Collection
+ Magento\Eav\Model\ResourceModel\Entity\Attribute\Grid\Collection
set_name
ASC
1
diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml
index a9770ed3c182..eea445041194 100644
--- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsell.xml
@@ -5,6 +5,10 @@
* See COPYING.txt for license details.
*/
-->
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml
index a4acf572caeb..2c400746c64f 100644
--- a/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/layout/catalog_product_upsellgrid.xml
@@ -5,6 +5,10 @@
* See COPYING.txt for license details.
*/
-->
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
index 00a1580923a7..ee67acd0ebd4 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
@@ -20,7 +20,7 @@
"categoryCheckboxTree": {
"dataUrl": "= $block->escapeUrl($block->getLoadTreeUrl()) ?>",
"divId": "= /* @noEscape */ $divId ?>",
- "rootVisible": = /* @noEscape */ $block->getRoot()->getIsVisible() ? 'true' : 'false' ?>,
+ "rootVisible": false,
"useAjax": = $block->escapeHtml($block->getUseAjax()) ?>,
"currentNodeId": = (int)$block->getCategoryId() ?>,
"jsFormObject": "= /* @noEscape */ $block->getJsFormObject() ?>",
@@ -28,7 +28,7 @@
"checked": "= $block->escapeHtml($block->getRoot()->getChecked()) ?>",
"allowdDrop": = /* @noEscape */ $block->getRoot()->getIsVisible() ? 'true' : 'false' ?>,
"rootId": = (int)$block->getRoot()->getId() ?>,
- "expanded": = (int)$block->getIsWasExpanded() ?>,
+ "expanded": true,
"categoryId": = (int)$block->getCategoryId() ?>,
"treeJson": = /* @noEscape */ $block->getTreeJson() ?>
}
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
index 9865589556e7..f448edc692ce 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
@@ -248,7 +248,7 @@
try {
response = JSON.parse(transport.responseText);
} catch (e) {
- console.warn('An error occured while parsing response');
+ console.warn('An error occurred while parsing response');
}
if (!response || !response['parameters']) {
@@ -302,6 +302,7 @@
}
//updateContent(url); //commented since ajax requests replaced with http ones to load a category
+ jQuery('#tree-div').find('.x-tree-node-el').first().remove();
}
jQuery(function () {
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
index dbe66ef1aecd..69737b8a37c1 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
@@ -160,7 +160,7 @@ jQuery(function()
loader: categoryLoader,
enableDD: false,
containerScroll: true,
- rootVisible: '= /* @escapeNotVerified */ $block->getRoot()->getIsVisible() ?>',
+ rootVisible: false,
useAjax: true,
currentNodeId: = (int) $block->getCategoryId() ?>,
addNodeTo: false
@@ -177,7 +177,7 @@ jQuery(function()
text: 'Psw',
draggable: false,
id: = (int) $block->getRoot()->getId() ?>,
- expanded: = (int) $block->getIsWasExpanded() ?>,
+ expanded: true,
category_id: = (int) $block->getCategoryId() ?>
};
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/form.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/form.phtml
index 74cf8f5f3a70..124194519b97 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/form.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/form.phtml
@@ -21,13 +21,13 @@
= $block->getChildHtml('form') ?>
-
-
-
= /* @escapeNotVerified */ $block->getFormScripts() ?>
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
index 8a5f1919f78b..195ac9242271 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml
@@ -40,13 +40,16 @@ function getFrontTab() {
function checkOptionsPanelVisibility(){
if($('manage-options-panel')){
- var panel = $('manage-options-panel').up('.fieldset');
+ var panel = $('manage-options-panel').up('.fieldset'),
+ activePanelClass = 'selected-type-options';
if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect')){
panel.show();
+ panel.addClass(activePanelClass);
}
else {
panel.hide();
+ panel.removeClass(activePanelClass);
}
}
}
@@ -55,7 +58,7 @@ function bindAttributeInputType()
{
checkOptionsPanelVisibility();
switchDefaultValueField();
- if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect' || $('frontend_input').value=='price')){
+ if($('frontend_input') && ($('frontend_input').value=='boolean' || $('frontend_input').value=='select' || $('frontend_input').value=='multiselect' || $('frontend_input').value=='price')){
if($('is_filterable') && !$('is_filterable').getAttribute('readonly')){
$('is_filterable').disabled = false;
}
@@ -331,7 +334,7 @@ if ($('is_required')) {
jQuery(function($) {
bindAttributeInputType();
- // @todo: refactor collapsable component
+ // @todo: refactor collapsible component
$('.attribute-popup .collapse, [data-role="advanced_fieldset-content"]')
.collapsable()
.collapse('hide');
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
index 54b945b48c10..9621b9a57168 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main.phtml
@@ -80,7 +80,7 @@
// set the root node
this.root = new Ext.tree.TreeNode({
text: 'ROOT',
- allowDrug:false,
+ allowDrag:false,
allowDrop:true,
id:'1'
});
@@ -188,6 +188,15 @@
for( j in config[i].children ) {
if(config[i].children[j].id) {
newNode = new Ext.tree.TreeNode(config[i].children[j]);
+
+ if (typeof newNode.ui.onTextChange === 'function') {
+ newNode.ui.onTextChange = function (_3, _4, _5) {
+ if (this.rendered) {
+ this.textNode.innerText = _4;
+ }
+ }
+ }
+ }
node.appendChild(newNode);
newNode.addListener('click', editSet.unregister);
}
@@ -195,13 +204,20 @@
}
}
}
- }
- editSet = function() {
- return {
- register : function(node) {
- editSet.currentNode = node;
- },
+
+ editSet = function () {
+ return {
+ register: function (node) {
+ editSet.currentNode = node;
+ if (typeof node.ui.onTextChange === 'function') {
+ node.ui.onTextChange = function (_3, _4, _5) {
+ if (this.rendered) {
+ this.textNode.innerText = _4;
+ }
+ }
+ }
+ },
unregister : function() {
editSet.currentNode = false;
@@ -293,6 +309,14 @@
allowDrag : true
});
+ if (typeof newNode.ui.onTextChange === 'function') {
+ newNode.ui.onTextChange = function (_3, _4, _5) {
+ if (this.rendered) {
+ this.textNode.innerText = _4;
+ }
+ }
+ }
+
TreePanels.root.appendChild(newNode);
newNode.addListener('beforemove', editSet.groupBeforeMove);
newNode.addListener('beforeinsert', editSet.groupBeforeInsert);
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml
index 223b3e9888ee..75f04eae8215 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml
@@ -2,4 +2,4 @@
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
- */;
+ */
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/attribute.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/attribute.phtml
index d1591d70945c..a3b0b32e4c29 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/attribute.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/attribute.phtml
@@ -11,11 +11,12 @@
-
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml
index efc06d675c36..64c8ba7dcf49 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/action/inventory.phtml
@@ -30,6 +30,13 @@
});
+getDefaultConfigValue('min_sale_qty');
+if (!is_numeric($defaultMinSaleQty)) {
+ $defaultMinSaleQty = json_decode($defaultMinSaleQty, true);
+ $defaultMinSaleQty = (float) $defaultMinSaleQty[\Magento\Customer\Api\Data\GroupInterface::CUST_GROUP_ALL] ?? 1;
+}
+?>
|