diff --git a/COPYING.txt b/COPYING.txt index d2cbcd01539dd7b2e6db5fe0e363f627befefb7a..2ba7d78d58a25298063b9f46ae944f882af86448 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -1,4 +1,4 @@ -Copyright © 2013-2017 Magento, Inc. +Copyright © 2013-2018 Magento, Inc. Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license diff --git a/README.md b/README.md index 1dd81a7eed27250fcebe0720c3a5ebdf3994ab8e..c72357db26d16847c5fff1cfb7c6c93af44eae29 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting edge, feature-rich eCommerce solution that gets results. ## Magento system requirements -[Magento system requirements](http://devdocs.magento.com/magento-system-requirements.html) +[Magento system requirements](http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements2.html) ## Install Magento To install Magento, see either: * [Magento DevBox](https://magento.com/tech-resources/download), the easiest way to get started with Magento. -* [Installation guide](http://devdocs.magento.com/guides/v2.0/install-gde/bk-install-guide.html) +* [Installation guide](http://devdocs.magento.com/guides/v2.2/install-gde/bk-install-guide.html) <h2>Contributing to the Magento 2 code base</h2> Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions. @@ -22,8 +22,8 @@ To learn about issues, click [here][2]. To open an issue, click [here][3]. To suggest documentation improvements, click [here][4]. -[1]: <http://devdocs.magento.com/guides/v2.0/contributor-guide/contributing.html> -[2]: <http://devdocs.magento.com/guides/v2.0/contributor-guide/contributing.html#report> +[1]: <http://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html> +[2]: <http://devdocs.magento.com/guides/v2.2/contributor-guide/contributing.html#report> [3]: <https://github.com/magento/magento2/issues> [4]: <http://devdocs.magento.com> diff --git a/app/bootstrap.php b/app/bootstrap.php index 3d474cea45432a6e42f72d2cabc2a57953260fac..e77c6d432c816c379ab226b366928d52cad3e426 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -14,12 +14,12 @@ error_reporting(E_ALL); if (!defined('PHP_VERSION_ID') || !(PHP_VERSION_ID === 70002 || PHP_VERSION_ID === 70004 || PHP_VERSION_ID >= 70006)) { if (PHP_SAPI == 'cli') { echo 'Magento supports 7.0.2, 7.0.4, and 7.0.6 or later. ' . - 'Please read http://devdocs.magento.com/guides/v1.0/install-gde/system-requirements.html'; + 'Please read http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html'; } else { echo <<<HTML <div style="font:12px/1.35em arial, helvetica, sans-serif;"> <p>Magento supports PHP 7.0.2, 7.0.4, and 7.0.6 or later. Please read - <a target="_blank" href="http://devdocs.magento.com/guides/v1.0/install-gde/system-requirements.html"> + <a target="_blank" href="http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html"> Magento System Requirements</a>. </div> HTML; diff --git a/app/code/Magento/Backend/Model/Menu/Item.php b/app/code/Magento/Backend/Model/Menu/Item.php index fe6564d24e891acda4bbc64e830b0964b26fe604..42febe94d0abffd4be864c142e2678cbdc14579c 100644 --- a/app/code/Magento/Backend/Model/Menu/Item.php +++ b/app/code/Magento/Backend/Model/Menu/Item.php @@ -467,15 +467,15 @@ class Item { return [ 'parent_id' => $this->_parentId, - 'module_name' => $this->_moduleName, + 'module' => $this->_moduleName, 'sort_index' => $this->_sortIndex, - 'depends_on_config' => $this->_dependsOnConfig, + 'dependsOnConfig' => $this->_dependsOnConfig, 'id' => $this->_id, 'resource' => $this->_resource, 'path' => $this->_path, 'action' => $this->_action, - 'depends_on_module' => $this->_dependsOnModule, - 'tooltip' => $this->_tooltip, + 'dependsOnModule' => $this->_dependsOnModule, + 'toolTip' => $this->_tooltip, 'title' => $this->_title, 'target' => $this->target, 'sub_menu' => isset($this->_submenu) ? $this->_submenu->toArray() : null @@ -492,15 +492,15 @@ class Item public function populateFromArray(array $data) { $this->_parentId = $this->_getArgument($data, 'parent_id'); - $this->_moduleName = $this->_getArgument($data, 'module_name', 'Magento_Backend'); + $this->_moduleName = $this->_getArgument($data, 'module', 'Magento_Backend'); $this->_sortIndex = $this->_getArgument($data, 'sort_index'); - $this->_dependsOnConfig = $this->_getArgument($data, 'depends_on_config'); + $this->_dependsOnConfig = $this->_getArgument($data, 'dependsOnConfig'); $this->_id = $this->_getArgument($data, 'id'); $this->_resource = $this->_getArgument($data, 'resource'); $this->_path = $this->_getArgument($data, 'path', ''); $this->_action = $this->_getArgument($data, 'action'); - $this->_dependsOnModule = $this->_getArgument($data, 'depends_on_module'); - $this->_tooltip = $this->_getArgument($data, 'tooltip', ''); + $this->_dependsOnModule = $this->_getArgument($data, 'dependsOnModule'); + $this->_tooltip = $this->_getArgument($data, 'toolTip'); $this->_title = $this->_getArgument($data, 'title'); $this->target = $this->_getArgument($data, 'target'); if (isset($data['sub_menu'])) { diff --git a/app/code/Magento/Backend/Test/Unit/Model/Menu/ItemTest.php b/app/code/Magento/Backend/Test/Unit/Model/Menu/ItemTest.php index 74368537c39c796f1590e0e693a11b37493ca21a..ad172cbfbd16501c6c73cdf1a677e8397003d4aa 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/Menu/ItemTest.php +++ b/app/code/Magento/Backend/Test/Unit/Model/Menu/ItemTest.php @@ -56,9 +56,9 @@ class ItemTest extends \PHPUnit\Framework\TestCase 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ]; protected function setUp() diff --git a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_constructor_data.php b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_constructor_data.php index b0c74461980a28eca2d93b84c3c8e6a31f4f9c63..82f07e264b96306de6e7e00bd6b51c16e140fa5a 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_constructor_data.php +++ b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_constructor_data.php @@ -12,21 +12,21 @@ return [ 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], [ 'parent_id' => null, - 'module_name' => 'Magento_Backend', + 'module' => 'Magento_Backend', 'sort_index' => null, - 'depends_on_config' => 'system/config/isEnabled', + 'dependsOnConfig' => 'system/config/isEnabled', 'id' => 'item', 'resource' => 'Magento_Config::config', 'path' => '', 'action' => '/system/config', - 'depends_on_module' => 'Magento_Backend', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'toolTip' => 'Item tooltip', 'title' => 'Item Title', 'sub_menu' => null, 'target' => null @@ -38,43 +38,43 @@ return [ 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', - 'depends_on_config' => null, + 'dependsOnConfig' => null, 'id' => '5', 'resource' => null, 'path' => null, 'action' => null, - 'depends_on_module' => null, - 'tooltip' => null, + 'dependsOnModule' => null, + 'toolTip' => null, 'title' => null, 'sub_menu' => [ 'id' => 'item', 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], ], [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', - 'depends_on_config' => null, + 'dependsOnConfig' => null, 'id' => '5', 'resource' => null, 'path' => '', 'action' => null, - 'depends_on_module' => null, - 'tooltip' => '', + 'dependsOnModule' => null, + 'toolTip' => '', 'title' => null, 'sub_menu' => ['submenuArray'], 'target' => null @@ -83,51 +83,51 @@ return [ 'data with submenu to constructor' => [ [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', - 'depends_on_config' => null, + 'dependsOnConfig' => null, 'id' => '5', 'resource' => null, 'path' => null, 'action' => null, - 'depends_on_module' => null, - 'tooltip' => null, + 'dependsOnModule' => null, + 'toolTip' => null, 'title' => null, 'sub_menu' => [ 'id' => 'item', 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], ], [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', 'sub_menu' => [ 'id' => 'item', 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], ], [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', - 'depends_on_config' => null, + 'dependsOnConfig' => null, 'id' => null, 'resource' => null, 'path' => '', 'action' => null, - 'depends_on_module' => null, - 'tooltip' => '', + 'dependsOnModule' => null, + 'toolTip' => '', 'title' => null, 'sub_menu' => ['submenuArray'], 'target' => null diff --git a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_data.php b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_data.php index 30a43b0158ae35601679808431d94668a2f847ba..b1a310d7d440b8d565d3999b9a441c0c8d6ad26a 100644 --- a/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_data.php +++ b/app/code/Magento/Backend/Test/Unit/Model/_files/menu_item_data.php @@ -11,22 +11,22 @@ return [ 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', 'sub_menu' => null, ], [ 'parent_id' => null, - 'module_name' => 'Magento_Backend', + 'module' => 'Magento_Backend', 'sort_index' => null, - 'depends_on_config' => 'system/config/isEnabled', + 'dependsOnConfig' => 'system/config/isEnabled', 'id' => 'item', 'resource' => 'Magento_Config::config', 'path' => '', 'action' => '/system/config', - 'depends_on_module' => 'Magento_Backend', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'toolTip' => 'Item tooltip', 'title' => 'Item Title', 'sub_menu' => null, 'target' => null @@ -35,46 +35,46 @@ return [ 'with submenu' => [ [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', - 'depends_on_config' => null, + 'dependsOnConfig' => null, 'id' => '5', 'resource' => null, 'path' => null, 'action' => null, - 'depends_on_module' => null, - 'tooltip' => null, + 'dependsOnModule' => null, + 'toolTip' => null, 'title' => null, 'sub_menu' => [ 'id' => 'item', 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], ], [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', - 'depends_on_config' => null, + 'dependsOnConfig' => null, 'id' => '5', 'resource' => null, 'path' => null, 'action' => null, - 'depends_on_module' => null, - 'tooltip' => '', + 'dependsOnModule' => null, + 'toolTip' => '', 'title' => null, 'sub_menu' => [ 'id' => 'item', 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], 'target' => null ] @@ -82,38 +82,38 @@ return [ 'small set of data' => [ [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', 'sub_menu' => [ 'id' => 'item', 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], ], [ 'parent_id' => '1', - 'module_name' => 'Magento_Module1', + 'module' => 'Magento_Module1', 'sort_index' => '50', - 'depends_on_config' => null, + 'dependsOnConfig' => null, 'id' => null, 'resource' => null, 'path' => '', 'action' => null, - 'depends_on_module' => null, - 'tooltip' => '', + 'dependsOnModule' => null, + 'toolTip' => '', 'title' => null, 'sub_menu' => [ 'id' => 'item', 'title' => 'Item Title', 'action' => '/system/config', 'resource' => 'Magento_Config::config', - 'depends_on_module' => 'Magento_Backend', - 'depends_on_config' => 'system/config/isEnabled', - 'tooltip' => 'Item tooltip', + 'dependsOnModule' => 'Magento_Backend', + 'dependsOnConfig' => 'system/config/isEnabled', + 'toolTip' => 'Item tooltip', ], 'target' => null ] diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 27fd16cc920dc587da14585cdc397c613889c555..b6324416d26d54a31c20334268e58ccf227e09c2 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -215,7 +215,7 @@ <label>European Union Countries</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> </field> - <field id="destinations" translate="label" type="multiselect" sortOrder="40" showInDefault="1" showInWebsite="0" showInStore="0"> + <field id="destinations" translate="label" type="multiselect" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="1"> <label>Top destinations</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> </field> diff --git a/app/code/Magento/Backup/Model/Db.php b/app/code/Magento/Backup/Model/Db.php index 776141249f92bc40dc448ff50fe443f7affd8891..8fbd5da1c98425a59fd9336e30a731f9786bce8e 100644 --- a/app/code/Magento/Backup/Model/Db.php +++ b/app/code/Magento/Backup/Model/Db.php @@ -154,7 +154,7 @@ class Db implements \Magento\Framework\Backup\Db\BackupDbInterface if ($tableStatus->getDataLength() > self::BUFFER_LENGTH) { if ($tableStatus->getAvgRowLength() < self::BUFFER_LENGTH) { - $limit = floor(self::BUFFER_LENGTH / $tableStatus->getAvgRowLength()); + $limit = floor(self::BUFFER_LENGTH / max($tableStatus->getAvgRowLength(), 1)); $multiRowsLength = ceil($tableStatus->getRows() / $limit); } else { $limit = 1; @@ -173,6 +173,7 @@ class Db implements \Magento\Framework\Backup\Db\BackupDbInterface } } $backup->write($this->getResource()->getTableForeignKeysSql()); + $backup->write($this->getResource()->getTableTriggersSql()); $backup->write($this->getResource()->getFooter()); $this->getResource()->commitTransaction(); diff --git a/app/code/Magento/Backup/Model/ResourceModel/Db.php b/app/code/Magento/Backup/Model/ResourceModel/Db.php index 3fbaf44ebb063bc275c735cf7479d64a1b81305f..f50a3c5b091ad2a7b5b891cfb0143d0055e51092 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/Db.php +++ b/app/code/Magento/Backup/Model/ResourceModel/Db.php @@ -114,6 +114,30 @@ class Db return $fkScript; } + /** + * Return triggers fro table(s) + * + * @param string|null $tableName + * @param bool $addDropIfExists + * @return string + */ + public function getTableTriggersSql($tableName = null, $addDropIfExists = true) + { + $triggerScript = ''; + if (!$tableName) { + $tables = $this->getTables(); + foreach ($tables as $table) { + $tableTriggerScript = $this->_resourceHelper->getTableTriggersSql($table, $addDropIfExists); + if (!empty($tableTriggerScript)) { + $triggerScript .= "\n" . $tableTriggerScript; + } + } + } else { + $triggerScript = $this->getTableTriggersSql($tableName, $addDropIfExists); + } + return $triggerScript; + } + /** * Retrieve table status * diff --git a/app/code/Magento/Backup/Model/ResourceModel/Helper.php b/app/code/Magento/Backup/Model/ResourceModel/Helper.php index b5418865339c34fb3826cde0672fc2cb4c5a9891..6d7084a87546c189034b79b3da6f6a560cc9ebe3 100644 --- a/app/code/Magento/Backup/Model/ResourceModel/Helper.php +++ b/app/code/Magento/Backup/Model/ResourceModel/Helper.php @@ -337,4 +337,40 @@ class Helper extends \Magento\Framework\DB\Helper { $this->getConnection()->query('SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ'); } + + /** + * Get create script for triggers + * + * @param string $tableName + * @param boolean $addDropIfExists + * @param boolean $stripDefiner + * @return string + */ + public function getTableTriggersSql($tableName, $addDropIfExists = false, $stripDefiner = true) + { + $script = "--\n-- Triggers structure for table `{$tableName}`\n--\n"; + $triggers = $this->getConnection()->query('SHOW TRIGGERS LIKE \''. $tableName . '\'')->fetchAll(); + + if (!$triggers) { + return ''; + } + foreach ($triggers as $trigger) { + if ($addDropIfExists) { + $script .= 'DROP TRIGGER IF EXISTS ' . $trigger['Trigger'] . ";\n"; + } + $script .= "delimiter ;;\n"; + + $triggerData = $this->getConnection()->query('SHOW CREATE TRIGGER '. $trigger['Trigger'])->fetch(); + if ($stripDefiner) { + $cleanedScript = preg_replace('/DEFINER=[^\s]*/', '', $triggerData['SQL Original Statement']); + $script .= $cleanedScript . "\n"; + } else { + $script .= $triggerData['SQL Original Statement'] . "\n"; + } + $script .= ";;\n"; + $script .= "delimiter ;\n"; + } + + return $script; + } } diff --git a/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeValidator.php b/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeValidator.php new file mode 100644 index 0000000000000000000000000000000000000000..d38b04e46b0dbde0f937bc913b5384448fe4db49 --- /dev/null +++ b/app/code/Magento/Braintree/Gateway/Validator/ErrorCodeValidator.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Gateway\Validator; + +use Braintree\Error\ErrorCollection; +use Braintree\Error\Validation; +use Braintree\Result\Error; +use Braintree\Result\Successful; + +/** + * Processes errors codes from Braintree response. + */ +class ErrorCodeValidator +{ + /** + * Invokes validation. + * + * @param Successful|Error $response + * @return array + */ + public function __invoke($response) + { + if (!$response instanceof Error) { + return [true, [__('Transaction is successful.')]]; + } + + return [false, $this->getErrorCodes($response->errors)]; + } + + /** + * Retrieves list of error codes from Braintree response. + * + * @param ErrorCollection $collection + * @return array + */ + private function getErrorCodes(ErrorCollection $collection) + { + $result = []; + /** @var Validation $error */ + foreach ($collection->deepAll() as $error) { + $result[] = $error->code; + } + + return $result; + } +} diff --git a/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php b/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php index 8028bace0cf242d373e329f0a90fbdf9a1fb2bc9..7bafa35e13185125b9bb10f32a8e9252b34fd2bb 100644 --- a/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php +++ b/app/code/Magento/Braintree/Gateway/Validator/GeneralResponseValidator.php @@ -18,16 +18,26 @@ class GeneralResponseValidator extends AbstractValidator */ protected $subjectReader; + /** + * @var ErrorCodeValidator + */ + private $errorCodeValidator; + /** * Constructor * * @param ResultInterfaceFactory $resultFactory * @param SubjectReader $subjectReader + * @param ErrorCodeValidator $errorCodeValidator */ - public function __construct(ResultInterfaceFactory $resultFactory, SubjectReader $subjectReader) - { + public function __construct( + ResultInterfaceFactory $resultFactory, + SubjectReader $subjectReader, + ErrorCodeValidator $errorCodeValidator + ) { parent::__construct($resultFactory); $this->subjectReader = $subjectReader; + $this->errorCodeValidator = $errorCodeValidator; } /** @@ -62,9 +72,10 @@ class GeneralResponseValidator extends AbstractValidator function ($response) { return [ property_exists($response, 'success') && $response->success === true, - [__('Braintree error response.')] + [$response->message ?? __('Braintree error response.')] ]; - } + }, + $this->errorCodeValidator ]; } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php index c8a46da504fef158a4a3d56137dc7d6debb3cc9f..de8423618ab8deb3c90f96d8f5753fd17af1a4ff 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/GeneralResponseValidatorTest.php @@ -5,12 +5,14 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; -use Braintree\Transaction; +use Braintree\Result\Error; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeValidator; +use Magento\Braintree\Gateway\Validator\GeneralResponseValidator; use Magento\Framework\Phrase; -use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\Validator\GeneralResponseValidator; -use Magento\Braintree\Gateway\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase { @@ -20,14 +22,9 @@ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase private $responseValidator; /** - * @var ResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $resultInterfaceFactoryMock; - - /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterfaceFactory|MockObject */ - private $subjectReaderMock; + private $resultInterfaceFactory; /** * Set up @@ -36,23 +33,20 @@ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->resultInterfaceFactoryMock = $this->getMockBuilder( - \Magento\Payment\Gateway\Validator\ResultInterfaceFactory::class - )->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class) + $this->resultInterfaceFactory = $this->getMockBuilder(ResultInterfaceFactory::class) ->disableOriginalConstructor() + ->setMethods(['create']) ->getMock(); $this->responseValidator = new GeneralResponseValidator( - $this->resultInterfaceFactoryMock, - $this->subjectReaderMock + $this->resultInterfaceFactory, + new SubjectReader(), + new ErrorCodeValidator() ); } /** - * Run test for validate method + * Checks a case when the validator processes successful and failed transactions. * * @param array $validationSubject * @param bool $isValid @@ -61,45 +55,52 @@ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase * * @dataProvider dataProviderTestValidate */ - public function testValidate(array $validationSubject, $isValid, $messages) + public function testValidate(array $validationSubject, bool $isValid, $messages) { - /** @var ResultInterface|\PHPUnit_Framework_MockObject_MockObject $resultMock */ - $resultMock = $this->createMock(ResultInterface::class); + $result = new Result($isValid, $messages); - $this->subjectReaderMock->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willReturn($validationSubject['response']['object']); - - $this->resultInterfaceFactoryMock->expects(self::once()) - ->method('create') + $this->resultInterfaceFactory->method('create') ->with([ 'isValid' => $isValid, 'failsDescription' => $messages ]) - ->willReturn($resultMock); + ->willReturn($result); - $actualMock = $this->responseValidator->validate($validationSubject); + $actual = $this->responseValidator->validate($validationSubject); - self::assertEquals($resultMock, $actualMock); + self::assertEquals($result, $actual); } /** + * Gets variations for different type of response. + * * @return array */ public function dataProviderTestValidate() { - $successTrue = new \stdClass(); - $successTrue->success = true; + $successTransaction = new \stdClass(); + $successTransaction->success = true; + + $failureTransaction = new \stdClass(); + $failureTransaction->success = false; + $failureTransaction->message = 'Transaction was failed.'; - $successFalse = new \stdClass(); - $successFalse->success = false; + $errors = [ + 'errors' => [ + [ + 'code' => 81804, + 'attribute' => 'base', + 'message' => 'Cannot process transaction.' + ] + ] + ]; + $errorTransaction = new Error(['errors' => $errors]); return [ [ 'validationSubject' => [ 'response' => [ - 'object' => $successTrue + 'object' => $successTransaction ], ], 'isValid' => true, @@ -108,12 +109,24 @@ class GeneralResponseValidatorTest extends \PHPUnit\Framework\TestCase [ 'validationSubject' => [ 'response' => [ - 'object' => $successFalse + 'object' => $failureTransaction + ] + ], + 'isValid' => false, + [ + __('Transaction was failed.') + ] + ], + [ + 'validationSubject' => [ + 'response' => [ + 'object' => $errorTransaction ] ], 'isValid' => false, [ - __('Braintree error response.') + __('Braintree error response.'), + 81804 ] ] ]; diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php index 03363b5463d78dd8f4774da5d8c54e96a68e65d5..a59bbb6f0d387b9d3cd53047db715a9c3cfbf3c0 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/PaymentNonceResponseValidatorTest.php @@ -5,15 +5,13 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; -use Braintree\Transaction; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeValidator; use Magento\Braintree\Gateway\Validator\PaymentNonceResponseValidator; -use Magento\Payment\Gateway\Validator\ResultInterface; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\SubjectReader; +use PHPUnit_Framework_MockObject_MockObject as MockObject; -/** - * Class PaymentNonceResponseValidatorTest - */ class PaymentNonceResponseValidatorTest extends \PHPUnit\Framework\TestCase { /** @@ -22,35 +20,24 @@ class PaymentNonceResponseValidatorTest extends \PHPUnit\Framework\TestCase private $validator; /** - * @var ResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var ResultInterfaceFactory|MockObject */ private $resultInterfaceFactory; - /** - * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject - */ - private $subjectReader; - protected function setUp() { $this->resultInterfaceFactory = $this->getMockBuilder(ResultInterfaceFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) - ->disableOriginalConstructor() - ->setMethods(['readResponseObject']) - ->getMock(); $this->validator = new PaymentNonceResponseValidator( $this->resultInterfaceFactory, - $this->subjectReader + new SubjectReader(), + new ErrorCodeValidator() ); } - /** - * @covers \Magento\Braintree\Gateway\Validator\PaymentNonceResponseValidator::validate - */ public function testFailedValidate() { $obj = new \stdClass(); @@ -61,23 +48,12 @@ class PaymentNonceResponseValidatorTest extends \PHPUnit\Framework\TestCase ] ]; - $this->subjectReader->expects(static::once()) - ->method('readResponseObject') - ->willReturn($obj); - - $result = $this->createMock(ResultInterface::class); - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => false, - 'failsDescription' => [ - __('Payment method nonce can\'t be retrieved.') - ] - ]) + $result = new Result(false, [__('Payment method nonce can\'t be retrieved.')]); + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->validator->validate($subject); - static::assertEquals($result, $actual); + self::assertEquals($result, $actual); } public function testValidateSuccess() @@ -93,20 +69,11 @@ class PaymentNonceResponseValidatorTest extends \PHPUnit\Framework\TestCase ] ]; - $this->subjectReader->expects(static::once()) - ->method('readResponseObject') - ->willReturn($obj); - - $result = $this->createMock(ResultInterface::class); - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => true, - 'failsDescription' => [] - ]) + $result = new Result(true); + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->validator->validate($subject); - static::assertEquals($result, $actual); + self::assertEquals($result, $actual); } } diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php index 4bd446079f9a70243944215ad450fede859cb59e..ba4d6cae062ee9f6eca0a111840ec6dbac2aa693 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Validator/ResponseValidatorTest.php @@ -5,15 +5,16 @@ */ namespace Magento\Braintree\Test\Unit\Gateway\Validator; +use Braintree\Result\Successful; use Braintree\Transaction; +use Magento\Braintree\Gateway\SubjectReader; +use Magento\Braintree\Gateway\Validator\ErrorCodeValidator; +use Magento\Braintree\Gateway\Validator\ResponseValidator; use Magento\Framework\Phrase; +use Magento\Payment\Gateway\Validator\Result; use Magento\Payment\Gateway\Validator\ResultInterface; use Magento\Payment\Gateway\Validator\ResultInterfaceFactory; -use Magento\Braintree\Gateway\Validator\ResponseValidator; -use Magento\Braintree\Gateway\SubjectReader; use PHPUnit_Framework_MockObject_MockObject as MockObject; -use Braintree\Result\Error; -use Braintree\Result\Successful; /** * Class ResponseValidatorTest @@ -30,11 +31,6 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase */ private $resultInterfaceFactory; - /** - * @var SubjectReader|MockObject - */ - private $subjectReader; - /** * Set up * @@ -46,13 +42,11 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->subjectReader = $this->getMockBuilder(SubjectReader::class) - ->disableOriginalConstructor() - ->getMock(); $this->responseValidator = new ResponseValidator( $this->resultInterfaceFactory, - $this->subjectReader + new SubjectReader(), + new ErrorCodeValidator() ); } @@ -65,11 +59,6 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase 'response' => null ]; - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willThrowException(new \InvalidArgumentException()); - $this->responseValidator->validate($validationSubject); } @@ -82,11 +71,6 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase 'response' => ['object' => null] ]; - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willThrowException(new \InvalidArgumentException()); - $this->responseValidator->validate($validationSubject); } @@ -103,19 +87,9 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase public function testValidate(array $validationSubject, $isValid, $messages) { /** @var ResultInterface|MockObject $result */ - $result = $this->createMock(ResultInterface::class); - - $this->subjectReader->expects(self::once()) - ->method('readResponseObject') - ->with($validationSubject) - ->willReturn($validationSubject['response']['object']); - - $this->resultInterfaceFactory->expects(self::once()) - ->method('create') - ->with([ - 'isValid' => $isValid, - 'failsDescription' => $messages - ]) + $result = new Result($isValid, $messages); + + $this->resultInterfaceFactory->method('create') ->willReturn($result); $actual = $this->responseValidator->validate($validationSubject); @@ -141,8 +115,6 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase $transactionDeclined->transaction = new \stdClass(); $transactionDeclined->transaction->status = Transaction::SETTLEMENT_DECLINED; - $errorResult = new Error(['errors' => []]); - return [ [ 'validationSubject' => [ @@ -175,18 +147,6 @@ class ResponseValidatorTest extends \PHPUnit\Framework\TestCase [ __('Wrong transaction status') ] - ], - [ - 'validationSubject' => [ - 'response' => [ - 'object' => $errorResult, - ] - ], - 'isValid' => false, - [ - __('Braintree error response.'), - __('Wrong transaction status') - ] ] ]; } diff --git a/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml b/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml new file mode 100644 index 0000000000000000000000000000000000000000..611f9372518fc7207f55b3922c00785f013cb735 --- /dev/null +++ b/app/code/Magento/Braintree/etc/adminhtml/braintree_error_mapping.xml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/error_mapping.xsd"> + <message_list> + <message code="81509" translate="true">Credit card type is not accepted by this merchant account.</message> + <message code="91504" translate="true">Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending.</message> + <message code="91505" translate="true">Credit transactions cannot be refunded.</message> + <message code="91506" translate="true">Cannot refund a transaction unless it is settled.</message> + <message code="91507" translate="true">Cannot submit for settlement unless status is authorized.</message> + <message code="91511" translate="true">Customer does not have any credit cards.</message> + <message code="91512" translate="true">Transaction has already been completely refunded.</message> + <message code="91517" translate="true">Payment instrument type is not accepted by this merchant account.</message> + <message code="91519" translate="true">Processor authorization code cannot be set unless for a voice authorization.</message> + <message code="91521" translate="true">Refund amount is too large.</message> + <message code="91522" translate="true">Settlement amount is too large.</message> + <message code="91530" translate="true">Cannot provide a billing address unless also providing a credit card.</message> + <message code="91538" translate="true">Cannot refund a transaction with a suspended merchant account.</message> + <message code="91547" translate="true">Merchant account does not support refunds.</message> + <message code="91574" translate="true">Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled.</message> + <message code="91576" translate="true">PayPal is not enabled for your merchant account.</message> + <message code="915102" translate="true">Partial settlements are not supported by this processor.</message> + <message code="915103" translate="true">Cannot submit for partial settlement.</message> + <message code="915148" translate="true">Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending.</message> + <message code="915149" translate="true">Too many concurrent attempts to refund this transaction. Try again later.</message> + <message code="915151" translate="true">Too many concurrent attempts to void this transaction. Try again later.</message> + </message_list> +</mapping> diff --git a/app/code/Magento/Braintree/etc/adminhtml/di.xml b/app/code/Magento/Braintree/etc/adminhtml/di.xml index 90fc927ed4f8026e6ee86fe22ec523a6eb3f69bf..bb5763469cad3a30beb0167bdb54fbad2677e4ba 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/di.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/di.xml @@ -45,6 +45,19 @@ </arguments> </virtualType> + <!-- Braintree commands --> + <type name="BraintreeVoidCommand"> + <arguments> + <argument name="errorMessageMapper" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper</argument> + </arguments> + </type> + <type name="BraintreeRefundCommand"> + <arguments> + <argument name="errorMessageMapper" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper</argument> + </arguments> + </type> + <!-- END Braintree commands --> + <type name="Magento\Vault\Model\Ui\Adminhtml\TokensConfigProvider"> <arguments> <argument name="tokenUiComponentProviders" xsi:type="array"> diff --git a/app/code/Magento/Braintree/etc/braintree_error_mapping.xml b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml new file mode 100644 index 0000000000000000000000000000000000000000..81da0a252e567896b0d9182ae9e5bbb9f52affb1 --- /dev/null +++ b/app/code/Magento/Braintree/etc/braintree_error_mapping.xml @@ -0,0 +1,64 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Payment:etc/error_mapping.xsd"> + <message_list> + <message code="81703" translate="true">Credit card type is not accepted by this merchant account.</message> + <message code="81706" translate="true">CVV is required.</message> + <message code="81707" translate="true">CVV must be 4 digits for American Express and 3 digits for other card types.</message> + <message code="81709" translate="true">Expiration date is required.</message> + <message code="81710" translate="true">Expiration date is invalid.</message> + <message code="81711" translate="true">Expiration year is invalid. It must be between 1975 and 2201.</message> + <message code="81712" translate="true">Expiration month is invalid.</message> + <message code="81713" translate="true">Expiration year is invalid.</message> + <message code="81714" translate="true">Credit card number is required.</message> + <message code="81715" translate="true">Credit card number is invalid.</message> + <message code="81716" translate="true">Credit card number must be 12-19 digits.</message> + <message code="81723" translate="true">Cardholder name is too long.</message> + <message code="81736" translate="true">CVV verification failed.</message> + <message code="81737" translate="true">Postal code verification failed.</message> + <message code="81750" translate="true">Credit card number is prohibited.</message> + <message code="81801" translate="true">Addresses must have at least one field filled in.</message> + <message code="81802" translate="true">Company is too long.</message> + <message code="81804" translate="true">Extended address is too long.</message> + <message code="81805" translate="true">First name is too long.</message> + <message code="81806" translate="true">Last name is too long.</message> + <message code="81807" translate="true">Locality is too long.</message> + <message code="81808" translate="true">Postal code is required.</message> + <message code="81809" translate="true">Postal code may contain no more than 9 letter or number characters.</message> + <message code="81810" translate="true">Region is too long.</message> + <message code="81811" translate="true">Street address is required.</message> + <message code="81812" translate="true">Street address is too long.</message> + <message code="81813" translate="true">Postal code can only contain letters, numbers, spaces, and hyphens.</message> + <message code="81827" translate="true">US state codes must be two characters to meet PayPal Seller Protection requirements.</message> + <message code="82901" translate="true">Incomplete PayPal account information.</message> + <message code="82903" translate="true">Invalid PayPal account information.</message> + <message code="82904" translate="true">PayPal Accounts are not accepted by this merchant account.</message> + <message code="91726" translate="true">Credit card type is not accepted by this merchant account.</message> + <message code="91734" translate="true">Credit card type is not accepted by this merchant account.</message> + <message code="91744" translate="true">Billing address format is invalid.</message> + <message code="91803" translate="true">Country name is not an accepted country.</message> + <message code="91814" translate="true">Country code is not accepted. Please contact the store administrator.</message> + <message code="91815" translate="true">Provided country information is inconsistent.</message> + <message code="91816" translate="true">Country code is not accepted. Please contact the store administrator.</message> + <message code="91817" translate="true">Country code is not accepted. Please contact the store administrator.</message> + <message code="91818" translate="true">Customer has already reached the maximum of 50 addresses.</message> + <message code="91819" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91820" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91821" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91822" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91823" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91824" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91825" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91826" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="91828" translate="true">Address is invalid. Please contact the store administrator.</message> + <message code="92910" translate="true">Error communicating with PayPal.</message> + <message code="92911" translate="true">PayPal authentication expired.</message> + <message code="92916" translate="true">Error executing PayPal order.</message> + <message code="92917" translate="true">Error executing PayPal billing agreement.</message> + </message_list> +</mapping> diff --git a/app/code/Magento/Braintree/etc/di.xml b/app/code/Magento/Braintree/etc/di.xml index 86c291181575437012da6db8ef0b2601eddd66bd..a834503c5882ae61f17d3164a7758d0767dcd641 100644 --- a/app/code/Magento/Braintree/etc/di.xml +++ b/app/code/Magento/Braintree/etc/di.xml @@ -193,6 +193,23 @@ </arguments> </type> + <virtualType name="Magento\Braintree\Gateway\ErrorMapper\VirtualConfigReader" type="Magento\Payment\Gateway\ErrorMapper\VirtualConfigReader"> + <arguments> + <argument name="fileName" xsi:type="string">braintree_error_mapping.xml</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Braintree\Gateway\ErrorMapper\VirtualMappingData" type="Magento\Payment\Gateway\ErrorMapper\MappingData"> + <arguments> + <argument name="reader" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualConfigReader</argument> + <argument name="cacheId" xsi:type="string">braintree_error_mapper</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper" type="Magento\Payment\Gateway\ErrorMapper\ErrorMessageMapper"> + <arguments> + <argument name="messageMapping" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualMappingData</argument> + </arguments> + </virtualType> + <!-- Braintree commands --> <virtualType name="BraintreeAuthorizeCommand" type="Magento\Payment\Gateway\Command\GatewayCommand"> <arguments> @@ -201,6 +218,7 @@ <argument name="client" xsi:type="object">Magento\Braintree\Gateway\Http\Client\TransactionSale</argument> <argument name="handler" xsi:type="object">BraintreeAuthorizationHandler</argument> <argument name="validator" xsi:type="object">Magento\Braintree\Gateway\Validator\ResponseValidator</argument> + <argument name="errorMessageMapper" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper</argument> </arguments> </virtualType> <virtualType name="BraintreeAuthorizeRequest" type="Magento\Payment\Gateway\Request\BuilderComposite"> @@ -240,6 +258,7 @@ <argument name="client" xsi:type="object">Magento\Braintree\Gateway\Http\Client\TransactionSubmitForSettlement</argument> <argument name="handler" xsi:type="object">Magento\Braintree\Gateway\Response\TransactionIdHandler</argument> <argument name="validator" xsi:type="object">Magento\Braintree\Gateway\Validator\ResponseValidator</argument> + <argument name="errorMessageMapper" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper</argument> </arguments> </virtualType> <virtualType name="BraintreeCaptureRequest" type="Magento\Payment\Gateway\Request\BuilderComposite"> @@ -258,6 +277,7 @@ <argument name="client" xsi:type="object">Magento\Braintree\Gateway\Http\Client\TransactionSale</argument> <argument name="handler" xsi:type="object">BraintreeVaultResponseHandler</argument> <argument name="validator" xsi:type="object">Magento\Braintree\Gateway\Validator\ResponseValidator</argument> + <argument name="errorMessageMapper" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper</argument> </arguments> </virtualType> <virtualType name="BraintreeVaultAuthorizeRequest" type="Magento\Payment\Gateway\Request\BuilderComposite"> @@ -296,6 +316,7 @@ <argument name="client" xsi:type="object">Magento\Braintree\Gateway\Http\Client\TransactionSale</argument> <argument name="handler" xsi:type="object">Magento\Braintree\Gateway\Response\TransactionIdHandler</argument> <argument name="validator" xsi:type="object">Magento\Braintree\Gateway\Validator\ResponseValidator</argument> + <argument name="errorMessageMapper" xsi:type="object">Magento\Braintree\Gateway\ErrorMapper\VirtualErrorMessageMapper</argument> </arguments> </virtualType> <virtualType name="BraintreeVaultCaptureRequest" type="Magento\Payment\Gateway\Request\BuilderComposite"> diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv index 116f459a1c1c83460e29701b54fd32d9313f8233..194ad14d497513785cfbc2bbb96e81cc669dfdd9 100644 --- a/app/code/Magento/Braintree/i18n/en_US.csv +++ b/app/code/Magento/Braintree/i18n/en_US.csv @@ -129,3 +129,65 @@ Amount,Amount "Refund Ids","Refund Ids" "Settlement Batch ID","Settlement Batch ID" Currency,Currency +"Addresses must have at least one field filled in.","Addresses must have at least one field filled in." +"Company is too long.","Company is too long." +"Extended address is too long.","Extended address is too long." +"First name is too long.","First name is too long." +"Last name is too long.","Last name is too long." +"Locality is too long.","Locality is too long." +"Postal code can only contain letters, numbers, spaces, and hyphens.","Postal code can only contain letters, numbers, spaces, and hyphens." +"Postal code is required.","Postal code is required." +"Postal code may contain no more than 9 letter or number characters.","Postal code may contain no more than 9 letter or number characters." +"Region is too long.","Region is too long." +"Street address is required.","Street address is required." +"Street address is too long.","Street address is too long." +"US state codes must be two characters to meet PayPal Seller Protection requirements.","US state codes must be two characters to meet PayPal Seller Protection requirements." +"Country name is not an accepted country.","Country name is not an accepted country." +"Provided country information is inconsistent.","Provided country information is inconsistent." +"Country code is not accepted. Please contact the store administrator.","Country code is not accepted. Please contact the store administrator." +"Customer has already reached the maximum of 50 addresses.","Customer has already reached the maximum of 50 addresses." +"Address is invalid. Please contact the store administrator.","Address is invalid. Please contact the store administrator." +"Address is invalid.","Address is invalid." +"Billing address format is invalid.","Billing address format is invalid." +"Cardholder name is too long.","Cardholder name is too long." +"Credit card type is not accepted by this merchant account.","Credit card type is not accepted by this merchant account." +"CVV is required.","CVV is required." +"CVV must be 4 digits for American Express and 3 digits for other card types.","CVV must be 4 digits for American Express and 3 digits for other card types." +"Expiration date is required.","Expiration date is required." +"Expiration date is invalid.","Expiration date is invalid." +"Expiration year is invalid. It must be between 1975 and 2201.","Expiration year is invalid. It must be between 1975 and 2201." +"Expiration month is invalid.","Expiration month is invalid." +"Expiration year is invalid.","Expiration year is invalid." +"Credit card number is required.","Credit card number is required." +"Credit card number is invalid.","Credit card number is invalid." +"Credit card number must be 12-19 digits.","Credit card number must be 12-19 digits." +"CVV verification failed.","CVV verification failed." +"Postal code verification failed.","Postal code verification failed." +"Credit card number is prohibited.","Credit card number is prohibited." +"Incomplete PayPal account information.","Incomplete PayPal account information." +"Invalid PayPal account information.","Invalid PayPal account information." +"PayPal Accounts are not accepted by this merchant account.","PayPal Accounts are not accepted by this merchant account." +"Error communicating with PayPal.","Error communicating with PayPal." +"PayPal authentication expired.","PayPal authentication expired." +"Error executing PayPal order.","Error executing PayPal order." +"Error executing PayPal billing agreement.","Error executing PayPal billing agreement." +"Cannot provide a billing address unless also providing a credit card.","Cannot provide a billing address unless also providing a credit card." +"Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending.","Transaction can only be voided if status is authorized, submitted_for_settlement, or - for PayPal - settlement_pending." +"Credit transactions cannot be refunded.","Credit transactions cannot be refunded." +"Cannot refund a transaction unless it is settled.","Cannot refund a transaction unless it is settled." +"Cannot submit for settlement unless status is authorized.","Cannot submit for settlement unless status is authorized." +"Customer does not have any credit cards.","Customer does not have any credit cards." +"Transaction has already been completely refunded.","Transaction has already been completely refunded." +"Payment instrument type is not accepted by this merchant account.","Payment instrument type is not accepted by this merchant account." +"Processor authorization code cannot be set unless for a voice authorization.","Processor authorization code cannot be set unless for a voice authorization." +"Refund amount is too large.","Refund amount is too large." +"Settlement amount is too large.","Settlement amount is too large." +"Cannot refund a transaction with a suspended merchant account.","Cannot refund a transaction with a suspended merchant account." +"Merchant account does not support refunds.","Merchant account does not support refunds." +"PayPal is not enabled for your merchant account.","PayPal is not enabled for your merchant account." +"Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled.","Cannot refund a transaction transaction in settling status on this merchant account. Try again after the transaction has settled." +"Cannot submit for partial settlement.","Cannot submit for partial settlement." +"Partial settlements are not supported by this processor.","Partial settlements are not supported by this processor." +"Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending.","Transaction can not be voided if status of a PayPal partial settlement child transaction is settlement_pending." +"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." \ No newline at end of file diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js index 73a7b10d5d30f976b19750ccbe1efeb9c90a821b..d078cacb96c2d2ebbad294796fbec86990c5c6be 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js @@ -79,6 +79,7 @@ define( */ onError: function (response) { braintree.showError($t('Payment ' + this.getTitle() + ' can\'t be initialized')); + this.isPlaceOrderActionAllowed(true); throw response.message; }, diff --git a/app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php b/app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php index 6688648a3c4fd6fabbc010e8440be6c6813a12b3..7f21d9e69c6e0eeee63ce6a2a44a3aa9d66d65bc 100644 --- a/app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php +++ b/app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php @@ -127,7 +127,7 @@ class Bundle } $options = []; foreach ($bundleOptionsData as $key => $optionData) { - if ((bool)$optionData['delete']) { + if (!empty($optionData['delete'])) { continue; } diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Option.php b/app/code/Magento/Bundle/Model/ResourceModel/Option.php index 2ad7e57f522d664dd79c621ec188c354688a008f..46fd8b910f6f1e1b57d36ea4959361145b233bca 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Option.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Option.php @@ -81,31 +81,39 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb { parent::_afterSave($object); - $condition = [ + $conditions = [ 'option_id = ?' => $object->getId(), 'store_id = ? OR store_id = 0' => $object->getStoreId(), 'parent_product_id = ?' => $object->getParentId() ]; $connection = $this->getConnection(); - $connection->delete($this->getTable('catalog_product_bundle_option_value'), $condition); - $data = new \Magento\Framework\DataObject(); - $data->setOptionId($object->getId()) - ->setStoreId($object->getStoreId()) - ->setParentProductId($object->getParentId()) - ->setTitle($object->getTitle()); - - $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); - - /** - * also saving default value if this store view scope - */ + if ($this->isOptionPresent($conditions)) { + $connection->update( + $this->getTable('catalog_product_bundle_option_value'), + [ + 'title' => $object->getTitle() + ], + $conditions + ); + } else { + $data = new \Magento\Framework\DataObject(); + $data->setOptionId($object->getId()) + ->setStoreId($object->getStoreId()) + ->setParentProductId($object->getParentId()) + ->setTitle($object->getTitle()); - if ($object->getStoreId()) { - $data->setStoreId(0); - $data->setTitle($object->getDefaultTitle()); $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); + + /** + * also saving default value if this store view scope + */ + if ($object->getStoreId()) { + $data->setStoreId(0); + $data->setTitle($object->getDefaultTitle()); + $connection->insert($this->getTable('catalog_product_bundle_option_value'), $data->getData()); + } } return $this; @@ -210,4 +218,26 @@ class Option extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb return $this; } + + /** + * Is Bundle option present in the database + * + * @param array $conditions + * + * @return bool + */ + private function isOptionPresent($conditions) + { + $connection = $this->getConnection(); + + $select = $connection->select()->from($this->getTable('catalog_product_bundle_option_value')); + foreach ($conditions as $condition => $conditionValue) { + $select->where($condition, $conditionValue); + } + $select->limit(1); + + $rowSelect = $connection->fetchRow($select); + + return (is_array($rowSelect) && !empty($rowSelect)); + } } diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml index b4c2c00e5ac4381379d52f3f123bfd25673e36c5..b9d075966c5d16a10c88e674777c7c99c2bbf370 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/creditmemo/items/renderer.phtml @@ -12,7 +12,6 @@ <?php $items = $block->getChildren($parentItem) ?> <?php $_order = $block->getItem()->getOrderItem()->getOrder() ?> -<?php $_count = count($items) ?> <?php $_index = 0 ?> <?php $_prevOptionId = '' ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml index f3866d7b1b6827ddd34f62d35278ab1bd5069bfe..e18d75ce77b9c79cf8843203dd220ec6ee89e5f1 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/invoice/items/renderer.phtml @@ -12,7 +12,6 @@ <?php $_order = $block->getItem()->getOrderItem()->getOrder() ?> <?php $items = $block->getChildren($parentItem) ?> -<?php $_count = count($items) ?> <?php $_index = 0 ?> <?php $_prevOptionId = '' ?> 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 84cbd54d3bdcc842ee72605170e62ed0a44b0bfb..063d66edb9e700928006c0b1fd0e89992d46938b 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 @@ -10,7 +10,6 @@ ?> <?php $parentItem = $block->getItem() ?> <?php $items = array_merge([$parentItem], $parentItem->getChildrenItems()); ?> -<?php $_count = count($items) ?> <?php $_index = 0 ?> <?php $_prevOptionId = '' ?> diff --git a/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml b/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml index bd99afc59a8c040d245b0d56e6ad2fec4757f86e..0cd39156b251314444acabeef54ab03d136b1218 100644 --- a/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml +++ b/app/code/Magento/Bundle/view/frontend/templates/sales/order/shipment/items/renderer.phtml @@ -12,7 +12,6 @@ <?php $parentItem = $block->getItem() ?> <?php $items = array_merge([$parentItem->getOrderItem()], $parentItem->getOrderItem()->getChildrenItems()) ?> <?php $shipItems = $block->getChildren($parentItem) ?> -<?php $_count = count($items) ?> <?php $_index = 0 ?> <?php $_prevOptionId = '' ?> diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php index 6f8a45c6ac7ed75a13026f6a5d9dc0696cf1cedd..a5b6b34d324f7ea0fe4d3648cec5521ec5e1d6e1 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php @@ -228,7 +228,7 @@ class Tree extends \Magento\Catalog\Block\Adminhtml\Category\AbstractCategory public function getLoadTreeUrl($expanded = null) { $params = ['_current' => true, 'id' => null, 'store' => null]; - if (is_null($expanded) && $this->_backendSession->getIsTreeWasExpanded() || $expanded == true) { + if ($expanded === null && $this->_backendSession->getIsTreeWasExpanded() || $expanded == true) { $params['expand_all'] = true; } return $this->getUrl('*/*/categoriesJson', $params); diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php index 64856a5c69dc7392146dc72acc27b35942cd07e0..339239ea491e091504e9ba8604ecd5b0e9468af1 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Options/Option.php @@ -313,9 +313,9 @@ class Option extends Widget $value['checkboxScopeTitle'] = $this->getCheckboxScopeHtml( $option->getOptionId(), 'title', - is_null($option->getStoreTitle()) + $option->getStoreTitle() === null ); - $value['scopeTitleDisabled'] = is_null($option->getStoreTitle()) ? 'disabled' : null; + $value['scopeTitleDisabled'] = $option->getStoreTitle() === null ? 'disabled' : null; } if ($option->getGroupByType() == ProductCustomOptionInterface::OPTION_GROUP_SELECT) { @@ -341,22 +341,22 @@ class Option extends Widget $value['optionValues'][$i]['checkboxScopeTitle'] = $this->getCheckboxScopeHtml( $_value->getOptionId(), 'title', - is_null($_value->getStoreTitle()), + $_value->getStoreTitle() === null, $_value->getOptionTypeId() ); - $value['optionValues'][$i]['scopeTitleDisabled'] = is_null( - $_value->getStoreTitle() + $value['optionValues'][$i]['scopeTitleDisabled'] = ( + $_value->getStoreTitle() === null ) ? 'disabled' : null; if ($scope == \Magento\Store\Model\Store::PRICE_SCOPE_WEBSITE) { $value['optionValues'][$i]['checkboxScopePrice'] = $this->getCheckboxScopeHtml( $_value->getOptionId(), 'price', - is_null($_value->getstorePrice()), + $_value->getstorePrice() === null, $_value->getOptionTypeId(), ['$(this).up(1).previous()'] ); - $value['optionValues'][$i]['scopePriceDisabled'] = is_null( - $_value->getStorePrice() + $value['optionValues'][$i]['scopePriceDisabled'] = ( + $_value->getStorePrice() === null ) ? 'disabled' : null; } } @@ -379,9 +379,9 @@ class Option extends Widget $value['checkboxScopePrice'] = $this->getCheckboxScopeHtml( $option->getOptionId(), 'price', - is_null($option->getStorePrice()) + $option->getStorePrice() === null ); - $value['scopePriceDisabled'] = is_null($option->getStorePrice()) ? 'disabled' : null; + $value['scopePriceDisabled'] = $option->getStorePrice() === null ? 'disabled' : null; } } $values[] = new \Magento\Framework\DataObject($value); diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php index 95d9b1ae612088146de159c28e769af4e75fbd17..3f9dac98033aa1a631cc63f279fb89df9cbbcab0 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Related.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Related.php @@ -121,7 +121,7 @@ class Related extends \Magento\Catalog\Block\Product\AbstractProduct implements * getIdentities() depends on _itemCollection populated, but it can be empty if the block is hidden * @see https://github.com/magento/magento2/issues/5897 */ - if (is_null($this->_itemCollection)) { + if ($this->_itemCollection === null) { $this->_prepareData(); } return $this->_itemCollection; diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php index f97d1a788dafb8909804178af162ba81873d9395..40afd443052629290b523a085be3ca8b5896f005 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php @@ -140,7 +140,7 @@ class Upsell extends \Magento\Catalog\Block\Product\AbstractProduct implements \ * getIdentities() depends on _itemCollection populated, but it can be empty if the block is hidden * @see https://github.com/magento/magento2/issues/5897 */ - if (is_null($this->_itemCollection)) { + if ($this->_itemCollection === null) { $this->_prepareData(); } return $this->_itemCollection; @@ -151,7 +151,7 @@ class Upsell extends \Magento\Catalog\Block\Product\AbstractProduct implements \ */ public function getItems() { - if (is_null($this->_items)) { + if ($this->_items === null) { $this->_items = $this->getItemCollection()->getItems(); } return $this->_items; diff --git a/app/code/Magento/Catalog/Helper/Product/View.php b/app/code/Magento/Catalog/Helper/Product/View.php index 46ac05168715b9644add204d151e22b6b3559648..ae948a362ab5a92fa67034d905dbbdc4e4dffd57 100644 --- a/app/code/Magento/Catalog/Helper/Product/View.php +++ b/app/code/Magento/Catalog/Helper/Product/View.php @@ -122,18 +122,18 @@ class View extends \Magento\Framework\App\Helper\AbstractHelper // Load default page handles and page configurations if ($params && $params->getBeforeHandles()) { foreach ($params->getBeforeHandles() as $handle) { - $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], $handle, false); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); } } - - $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); + $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], null, false); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); if ($params && $params->getAfterHandles()) { foreach ($params->getAfterHandles() as $handle) { - $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], $handle, false); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku], $handle); } } diff --git a/app/code/Magento/Catalog/Model/Config.php b/app/code/Magento/Catalog/Model/Config.php index 227821463b7f055e9541d00b91926135c3b0dd14..b3b5204887ea10fb2e4aaa08d517067e712a823b 100644 --- a/app/code/Magento/Catalog/Model/Config.php +++ b/app/code/Magento/Catalog/Model/Config.php @@ -407,7 +407,7 @@ class Config extends \Magento\Eav\Model\Config */ public function getProductAttributes() { - if (is_null($this->_productAttributes)) { + if ($this->_productAttributes === null) { $this->_productAttributes = array_keys($this->getAttributesUsedInProductListing()); } return $this->_productAttributes; @@ -430,7 +430,7 @@ class Config extends \Magento\Eav\Model\Config */ public function getAttributesUsedInProductListing() { - if (is_null($this->_usedInProductListing)) { + if ($this->_usedInProductListing === null) { $this->_usedInProductListing = []; $entityType = \Magento\Catalog\Model\Product::ENTITY; $attributesData = $this->_getResource()->setStoreId($this->getStoreId())->getAttributesUsedInListing(); @@ -453,7 +453,7 @@ class Config extends \Magento\Eav\Model\Config */ public function getAttributesUsedForSortBy() { - if (is_null($this->_usedForSortBy)) { + if ($this->_usedForSortBy === null) { $this->_usedForSortBy = []; $entityType = \Magento\Catalog\Model\Product::ENTITY; $attributesData = $this->_getResource()->getAttributesUsedForSortBy(); diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/Price.php b/app/code/Magento/Catalog/Model/Layer/Filter/Price.php index ec5e2bff81ab36e0fdb75e398f4bbc52b0745036..68ef96c0f36a1eed800128180de34ba22f963200 100644 --- a/app/code/Magento/Catalog/Model/Layer/Filter/Price.php +++ b/app/code/Magento/Catalog/Model/Layer/Filter/Price.php @@ -150,7 +150,7 @@ class Price extends \Magento\Catalog\Model\Layer\Filter\AbstractFilter public function getCustomerGroupId() { $customerGroupId = $this->_getData('customer_group_id'); - if (is_null($customerGroupId)) { + if ($customerGroupId === null) { $customerGroupId = $this->_customerSession->getCustomerGroupId(); } @@ -176,7 +176,7 @@ class Price extends \Magento\Catalog\Model\Layer\Filter\AbstractFilter public function getCurrencyRate() { $rate = $this->_getData('currency_rate'); - if (is_null($rate)) { + if ($rate === null) { $rate = $this->_storeManager->getStore($this->getStoreId()) ->getCurrentCurrencyRate(); } 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 cd686c05908ce724cb2c61f59cc3e0694e47dd83..84770a4a93ed41461eb55ad8b2358be1a4ae05dc 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 @@ -91,7 +91,7 @@ abstract class AbstractGroupPrice extends Price */ protected function _getWebsiteCurrencyRates() { - if (is_null($this->_rates)) { + if ($this->_rates === null) { $this->_rates = []; $baseCurrency = $this->_config->getValue( \Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE, diff --git a/app/code/Magento/Catalog/Model/Product/Option/Value.php b/app/code/Magento/Catalog/Model/Product/Option/Value.php index d92646769b13b2dfba439393f852e2540b14bcb9..10aae63ed349ce51bcbf94cd1185c4676c068d76 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Value.php @@ -179,7 +179,7 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu */ public function getProduct() { - if (is_null($this->_product)) { + if ($this->_product === null) { $this->_product = $this->getOption()->getProduct(); } return $this->_product; diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 83feea903f99343dae5f75dc01516945eae1e3d4..f260b01c02ef41d6b652c39750ed795c2f36e01f 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -6,10 +6,8 @@ */ namespace Magento\Catalog\Model; -use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap; use Magento\Catalog\Model\ResourceModel\Product\Collection; -use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\Data\ImageContentInterfaceFactory; use Magento\Framework\Api\ImageContentValidatorInterface; use Magento\Framework\Api\ImageProcessorInterface; @@ -18,10 +16,8 @@ use Magento\Framework\DB\Adapter\ConnectionException; use Magento\Framework\DB\Adapter\DeadlockException; use Magento\Framework\DB\Adapter\LockWaitException; use Magento\Framework\Exception\CouldNotSaveException; -use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Exception\StateException; use Magento\Framework\Exception\ValidatorException; /** @@ -116,11 +112,15 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected $fileSystem; /** + * @deprecated + * @see \Magento\Catalog\Model\MediaGalleryProcessor * @var ImageContentInterfaceFactory */ protected $contentFactory; /** + * @deprecated + * @see \Magento\Catalog\Model\MediaGalleryProcessor * @var ImageProcessorInterface */ protected $imageProcessor; @@ -131,7 +131,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa protected $extensionAttributesJoinProcessor; /** - * @var \Magento\Catalog\Model\Product\Gallery\Processor + * @var ProductRepository\MediaGalleryProcessor */ protected $mediaGalleryProcessor; @@ -329,6 +329,9 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa unset($productData['media_gallery']); if ($createNew) { $product = $this->productFactory->create(); + if (isset($productData['price']) && !isset($productData['product_type'])) { + $product->setTypeId(Product\Type::TYPE_SIMPLE); + } if ($this->storeManager->hasSingleStore()) { $product->setWebsiteIds([$this->storeManager->getStore(true)->getWebsiteId()]); } @@ -375,53 +378,6 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa $product->setWebsiteIds($websiteIds); } - /** - * @param ProductInterface $product - * @param array $newEntry - * @return $this - * @throws InputException - * @throws StateException - * @throws \Magento\Framework\Exception\LocalizedException - */ - 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); - - if (!$product->hasGalleryAttribute()) { - throw new StateException(__('Requested product does not support images.')); - } - - $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; - } - /** * Process product links, creating new links, updating and deleting existing links * @@ -480,67 +436,6 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa return $this; } - /** - * 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 $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['id'])) { - $entriesById[$entry['id']] = $entry; - } else { - $newEntries[] = $entry; - } - } - foreach ($existingMediaGallery as $key => &$existingEntry) { - if (isset($entriesById[$existingEntry['value_id']])) { - $updatedEntry = $entriesById[$existingEntry['value_id']]; - if (array_key_exists('file', $updatedEntry) && $updatedEntry['file'] === null) { - unset($updatedEntry['file']); - } - $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); - } else { - //set the removed flag - $existingEntry['removed'] = true; - } - } - unset($existingEntry); - $product->setData('media_gallery', ["images" => $existingMediaGallery]); - } else { - $newEntries = $mediaGalleryEntries; - } - - $this->getMediaGalleryProcessor()->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); - $images = $product->getMediaGallery('images'); - if ($images) { - foreach ($images as $image) { - if (!isset($image['removed']) && !empty($image['types'])) { - $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']); - } - } - } - $this->processEntries($product, $newEntries, $entriesById); - - return $this; - } - /** * {@inheritdoc} * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -577,7 +472,10 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa $this->processLinks($product, $productLinks); if (isset($productDataArray['media_gallery_entries'])) { - $this->processMediaGallery($product, $productDataArray['media_gallery_entries']); + $this->getMediaGalleryProcessor()->processMediaGallery( + $product, + $productDataArray['media_gallery_entries'] + ); } if (!$product->getOptionsReadonly()) { @@ -749,13 +647,13 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa } /** - * @return Product\Gallery\Processor + * @return ProductRepository\MediaGalleryProcessor */ private function getMediaGalleryProcessor() { if (null === $this->mediaGalleryProcessor) { $this->mediaGalleryProcessor = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\Product\Gallery\Processor::class); + ->get(ProductRepository\MediaGalleryProcessor::class); } return $this->mediaGalleryProcessor; } @@ -775,60 +673,4 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa } return $this->collectionProcessor; } - - /** - * Convert extension attribute for product media gallery. - * - * @param array $newEntry - * @param array $extensionAttributes - * @return void - */ - private function processExtensionAttributes(array &$newEntry, array $extensionAttributes) - { - foreach ($extensionAttributes as $code => $value) { - if (is_array($value)) { - $this->processExtensionAttributes($newEntry, $value); - } else { - $newEntry[$code] = $value; - } - } - unset($newEntry['extension_attributes']); - } - - /** - * 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 - * @return void - */ - private function processEntries(ProductInterface $product, array $newEntries, array $entriesById) - { - foreach ($newEntries as $newEntry) { - if (!isset($newEntry['content'])) { - throw new InputException(__('The image content is not valid.')); - } - /** @var ImageContentInterface $contentDataObject */ - $contentDataObject = $this->contentFactory->create() - ->setName($newEntry['content'][ImageContentInterface::NAME]) - ->setBase64EncodedData($newEntry['content'][ImageContentInterface::BASE64_ENCODED_DATA]) - ->setType($newEntry['content'][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)); - if (isset($newEntry['extension_attributes'])) { - $this->processExtensionAttributes($newEntry, $newEntry['extension_attributes']); - } - $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); - $entriesById[$newEntryId] = $newEntry; - $finalGallery['images'][$newEntryId] = $newEntry; - $product->setData('media_gallery', $finalGallery); - } - } } 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 0000000000000000000000000000000000000000..4cc31d98fdfc2fd1d0d1ddc9487c0c5a1e301f6b --- /dev/null +++ b/app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php @@ -0,0 +1,218 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\ProductRepository; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product\Gallery\Processor; +use Magento\Framework\Api\Data\ImageContentInterface; +use Magento\Framework\Api\Data\ImageContentInterfaceFactory; +use Magento\Framework\Api\ImageProcessorInterface; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\StateException; + +/** + * Process Media gallery data for ProductRepository before save product. + */ +class MediaGalleryProcessor +{ + /** + * @var Processor + */ + private $processor; + + /** + * @var ImageContentInterfaceFactory + */ + private $contentFactory; + + /** + * @var ImageProcessorInterface + */ + private $imageProcessor; + + /** + * MediaGalleryProcessor constructor. + * + * @param Processor $processor + * @param ImageContentInterfaceFactory $contentFactory + * @param ImageProcessorInterface $imageProcessor + */ + public function __construct( + Processor $processor, + ImageContentInterfaceFactory $contentFactory, + ImageProcessorInterface $imageProcessor + ) { + $this->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 + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function processMediaGallery(ProductInterface $product, array $mediaGalleryEntries) + { + $existingMediaGallery = $product->getMediaGallery('images'); + $newEntries = []; + $entriesById = []; + if (!empty($existingMediaGallery)) { + foreach ($mediaGalleryEntries as $entry) { + if (isset($entry['id'])) { + $entriesById[$entry['id']] = $entry; + } else { + $newEntries[] = $entry; + } + } + foreach ($existingMediaGallery as $key => &$existingEntry) { + if (isset($entriesById[$existingEntry['value_id']])) { + $updatedEntry = $entriesById[$existingEntry['value_id']]; + if (array_key_exists('file', $updatedEntry) && $updatedEntry['file'] === null) { + unset($updatedEntry['file']); + } + $existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry); + } else { + //set the removed flag. + $existingEntry['removed'] = true; + } + } + unset($existingEntry); + $product->setData('media_gallery', ["images" => $existingMediaGallery]); + } else { + $newEntries = $mediaGalleryEntries; + } + + $this->processor->clearMediaAttribute($product, array_keys($product->getMediaAttributes())); + $images = $product->getMediaGallery('images'); + if ($images) { + foreach ($images as $image) { + if (!isset($image['removed']) && !empty($image['types'])) { + $this->processor->setMediaAttribute($product, $image['types'], $image['file']); + } + } + } + $this->processEntries($product, $newEntries, $entriesById); + } + + /** + * 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 + * @return void + */ + private function processEntries(ProductInterface $product, array $newEntries, array $entriesById) + { + foreach ($newEntries as $newEntry) { + if (!isset($newEntry['content'])) { + throw new InputException(__('The image content is not valid.')); + } + /** @var ImageContentInterface $contentDataObject */ + $contentDataObject = $this->contentFactory->create() + ->setName($newEntry['content'][ImageContentInterface::NAME]) + ->setBase64EncodedData($newEntry['content'][ImageContentInterface::BASE64_ENCODED_DATA]) + ->setType($newEntry['content'][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)); + if (isset($newEntry['extension_attributes'])) { + $this->processExtensionAttributes($newEntry, $newEntry['extension_attributes']); + } + $newEntry = array_replace_recursive($newEntry, $finalGallery['images'][$newEntryId]); + $entriesById[$newEntryId] = $newEntry; + $finalGallery['images'][$newEntryId] = $newEntry; + $product->setData('media_gallery', $finalGallery); + } + } + + /** + * Save gallery entry as image. + * + * @param ProductInterface $product + * @param array $newEntry + * @return void + * @throws InputException + * @throws StateException + * @throws \Magento\Framework\Exception\LocalizedException + */ + private 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); + + if (!$product->hasGalleryAttribute()) { + throw new StateException(__('Requested product does not support images.')); + } + + $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'], + ] + ); + } + + /** + * Convert extension attribute for product media gallery. + * + * @param array $newEntry + * @param array $extensionAttributes + * @return void + */ + private function processExtensionAttributes(array &$newEntry, array $extensionAttributes) + { + foreach ($extensionAttributes as $code => $value) { + if (is_array($value)) { + $this->processExtensionAttributes($newEntry, $value); + } else { + $newEntry[$code] = $value; + } + } + unset($newEntry['extension_attributes']); + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 58e8424663c83cf226ccc7d1a0c8fa5b200f25b3..925c9fe60855d247fef190d9b4bce3322eb0cafb 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -1092,7 +1092,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac protected function _getSelectCountSql($select = null, $resetLeftJoins = true) { $this->_renderFilters(); - $countSelect = is_null($select) ? $this->_getClearSelect() : $this->_buildClearSelect($select); + $countSelect = $select === null ? $this->_getClearSelect() : $this->_buildClearSelect($select); $countSelect->columns('COUNT(DISTINCT e.entity_id)'); if ($resetLeftJoins) { $countSelect->resetJoinLeft(); @@ -1435,7 +1435,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac $ids = $this->_allIdsCache; } - if (is_null($ids)) { + if ($ids === null) { $ids = $this->getAllIds(); $this->setAllIdsCache($ids); } @@ -1466,17 +1466,17 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac { $this->_productLimitationFilters->setUsePriceIndex(true); - if (!isset($this->_productLimitationFilters['customer_group_id']) && is_null($customerGroupId)) { + if (!isset($this->_productLimitationFilters['customer_group_id']) && $customerGroupId === null) { $customerGroupId = $this->_customerSession->getCustomerGroupId(); } - if (!isset($this->_productLimitationFilters['website_id']) && is_null($websiteId)) { + if (!isset($this->_productLimitationFilters['website_id']) && $websiteId === null) { $websiteId = $this->_storeManager->getStore($this->getStoreId())->getWebsiteId(); } - if (!is_null($customerGroupId)) { + if ($customerGroupId !== null) { $this->_productLimitationFilters['customer_group_id'] = $customerGroupId; } - if (!is_null($websiteId)) { + if ($websiteId !== null) { $this->_productLimitationFilters['website_id'] = $websiteId; } @@ -2347,7 +2347,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ public function getMaxPrice() { - if (is_null($this->_maxPrice)) { + if ($this->_maxPrice === null) { $this->_prepareStatisticsData(); } @@ -2361,7 +2361,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ public function getMinPrice() { - if (is_null($this->_minPrice)) { + if ($this->_minPrice === null) { $this->_prepareStatisticsData(); } @@ -2375,7 +2375,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ public function getPriceStandardDeviation() { - if (is_null($this->_priceStandardDeviation)) { + if ($this->_priceStandardDeviation === null) { $this->_prepareStatisticsData(); } @@ -2389,7 +2389,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac */ public function getPricesCount() { - if (is_null($this->_pricesCount)) { + if ($this->_pricesCount === null) { $this->_prepareStatisticsData(); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php index b4f7e43387d0efd50604054cc69bbb8ea74748d8..ee1df8f23424d7fc14b65da13fa461b3304e10e4 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php @@ -87,6 +87,7 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()) ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->order('t.min_price ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php index f018e2b148f156a01006f187a8595345a8a2b9f3..8841b6059c46ff2b024edd9b90d4e431ce7f0466 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php @@ -95,6 +95,7 @@ class LinkedProductSelectBuilderByBasePrice implements LinkedProductSelectBuilde ->where('t.attribute_id = ?', $priceAttribute->getAttributeId()) ->where('t.value IS NOT NULL') ->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php index b4459cd1eea078e65aa3d3b7a20f58096c1ec39b..5c47185a85bf407d2c13215c379611f2d144cb0d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php @@ -139,6 +139,7 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui 'special_to.value IS NULL OR ' . $connection->getDatePartSql('special_to.value') .' >= ?', $currentDate )->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $specialPrice = $this->baseSelectProcessor->process($specialPrice); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php index 79323e57b033e36c6ede529e66b1d52cab9ef656..37281193d6a1b3509779bd18ec8a2008b14c0305 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php @@ -97,6 +97,7 @@ class LinkedProductSelectBuilderByTierPrice implements LinkedProductSelectBuilde ->where('t.all_groups = 1 OR customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.qty = ?', 1) ->order('t.value ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..02773b2fb3d703e507101ca3ad899c53fdacc84f --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepository/MediaGalleryProcessorTest.php @@ -0,0 +1,227 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\Unit\Model\ProductRepository; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Gallery\Processor; +use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor; +use Magento\Framework\Api\Data\ImageContentInterface; +use Magento\Framework\Api\Data\ImageContentInterfaceFactory; +use Magento\Framework\Api\ImageProcessorInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit\Framework\TestCase; + +/** + * Provide tests for ProductRepository/MediaGalleryProcessor. + */ +class MediaGalleryProcessorTest extends TestCase +{ + /** + * Test subject. + * + * @var MediaGalleryProcessor + */ + private $model; + + /** + * @var Processor|\PHPUnit_Framework_MockObject_MockObject + */ + private $processor; + + /** + * @var ImageContentInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $contentFactory; + + /** + * @var ImageProcessorInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $imageProcessor; + + /** + * @var Product|\PHPUnit_Framework_MockObject_MockObject + */ + private $product; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->product = $this->createPartialMock( + \Magento\Catalog\Model\Product::class, + [ + 'hasGalleryAttribute', + 'getMediaConfig', + 'getMediaAttributes', + 'getMediaGalleryEntries', + ] + ); + $this->product->expects($this->any()) + ->method('hasGalleryAttribute') + ->willReturn(true); + $this->processor = $this->getMockBuilder(Processor::class) + ->disableOriginalConstructor() + ->getMock(); + $this->contentFactory = $this->getMockBuilder(ImageContentInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->imageProcessor = $this->getMockBuilder(ImageProcessorInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $objectManager = new ObjectManager($this); + $this->model = $objectManager->getObject( + MediaGalleryProcessor::class, + [ + 'processor' => $this->processor, + 'contentFactory' => $this->contentFactory, + 'imageProcessor' => $this->imageProcessor, + ] + ); + } + + /** + * Test add image. + * + * @return void + */ + public function testProcessWithNewMediaEntry() + { + $mediaGalleryEntries = [ + [ + 'value_id' => null, + 'label' => 'label_text', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + 'content' => [ + ImageContentInterface::NAME => 'filename', + ImageContentInterface::TYPE => 'image/jpeg', + ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', + ], + 'media_type' => 'media_type', + ], + ]; + + //setup media attribute backend. + $mediaTmpPath = '/tmp'; + $absolutePath = '/a/b/filename.jpg'; + $mediaConfigMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) + ->disableOriginalConstructor() + ->getMock(); + $mediaConfigMock->expects($this->once()) + ->method('getTmpMediaShortUrl') + ->with($absolutePath) + ->willReturn($mediaTmpPath . $absolutePath); + $this->product->setData('media_gallery', ['images' => $mediaGalleryEntries]); + $this->product->expects($this->any()) + ->method('getMediaAttributes') + ->willReturn(['image' => 'imageAttribute', 'small_image' => 'small_image_attribute']); + $this->product->expects($this->once()) + ->method('getMediaConfig') + ->willReturn($mediaConfigMock); + $this->processor->expects($this->once())->method('clearMediaAttribute') + ->with($this->product, ['image', 'small_image']); + + //verify new entries. + $contentDataObject = $this->getMockBuilder(\Magento\Framework\Api\ImageContent::class) + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $this->contentFactory->expects($this->once()) + ->method('create') + ->willReturn($contentDataObject); + + $this->imageProcessor->expects($this->once()) + ->method('processImageContent') + ->willReturn($absolutePath); + + $imageFileUri = 'imageFileUri'; + $this->processor->expects($this->once())->method('addImage') + ->with($this->product, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) + ->willReturn($imageFileUri); + $this->processor->expects($this->once())->method('updateImage') + ->with( + $this->product, + $imageFileUri, + [ + 'label' => 'label_text', + 'position' => 10, + 'disabled' => false, + 'media_type' => 'media_type', + ] + ); + + $this->model->processMediaGallery($this->product, $mediaGalleryEntries); + } + + /** + * Test update(delete) images. + */ + public function testProcessExistingWithMediaGalleryEntries() + { + //update one entry, delete one entry. + $newEntries = [ + [ + 'id' => 5, + 'label' => 'new_label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + ], + ]; + + $existingMediaGallery = [ + 'images' => [ + [ + 'value_id' => 5, + 'label' => 'label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => true, + ], + [ + 'value_id' => 6, //will be deleted. + 'file' => 'filename2', + ], + ], + ]; + + $expectedResult = [ + [ + 'value_id' => 5, + 'id' => 5, + 'label' => 'new_label_text', + 'file' => 'filename1', + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + ], + [ + 'value_id' => 6, //will be deleted. + 'file' => 'filename2', + 'removed' => true, + ], + ]; + + $this->product->setData('media_gallery', $existingMediaGallery); + $this->product->expects($this->any()) + ->method('getMediaAttributes') + ->willReturn(['image' => 'filename1', 'small_image' => 'filename2']); + + $this->processor->expects($this->once())->method('clearMediaAttribute') + ->with($this->product, ['image', 'small_image']); + $this->processor->expects($this->once()) + ->method('setMediaAttribute') + ->with($this->product, ['image', 'small_image'], 'filename1'); + $this->model->processMediaGallery($this->product, $newEntries); + $this->assertEquals($expectedResult, $this->product->getMediaGallery('images')); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index a220b9a5768fed1b0d043cb7d1629439e657fc7d..14c84f4781a3aa962dbbf04f2ec60d1e11954915 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -9,6 +9,7 @@ namespace Magento\Catalog\Test\Unit\Model; +use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface; use Magento\Framework\DB\Adapter\ConnectionException; @@ -139,7 +140,7 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase protected $storeManagerMock; /** - * @var \Magento\Catalog\Model\Product\Gallery\Processor|\PHPUnit_Framework_MockObject_MockObject + * @var MediaGalleryProcessor|\PHPUnit_Framework_MockObject_MockObject */ protected $mediaGalleryProcessor; @@ -234,7 +235,7 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase $storeMock->expects($this->any())->method('getCode')->willReturn(\Magento\Store\Model\Store::ADMIN_CODE); $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock); - $this->mediaGalleryProcessor = $this->createMock(\Magento\Catalog\Model\Product\Gallery\Processor::class); + $this->mediaGalleryProcessor = $this->createMock(MediaGalleryProcessor::class); $this->collectionProcessorMock = $this->getMockBuilder(CollectionProcessorInterface::class) ->getMock(); @@ -1174,7 +1175,21 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase ] ] ]; - + $expectedEntriesData = [ + [ + 'id' => null, + 'label' => "label_text", + 'position' => 10, + 'disabled' => false, + 'types' => ['image', 'small_image'], + 'content' => [ + ImageContentInterface::NAME => 'filename', + ImageContentInterface::TYPE => 'image/jpeg', + ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content', + ], + 'media_type' => 'media_type', + ], + ]; $this->setupProductMocksForSave(); //media gallery data $this->productData['media_gallery_entries'] = [ @@ -1198,56 +1213,8 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase ->will($this->returnValue($this->productData)); $this->initializedProductMock->setData('media_gallery', $newEntriesData); - $this->initializedProductMock->expects($this->any()) - ->method('getMediaAttributes') - ->willReturn(["image" => "imageAttribute", "small_image" => "small_image_attribute"]); - - //setup media attribute backend - $mediaTmpPath = '/tmp'; - $absolutePath = '/a/b/filename.jpg'; - - $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image']); - - $mediaConfigMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) - ->disableOriginalConstructor() - ->getMock(); - $mediaConfigMock->expects($this->once()) - ->method('getTmpMediaShortUrl') - ->with($absolutePath) - ->willReturn($mediaTmpPath . $absolutePath); - $this->initializedProductMock->expects($this->once()) - ->method('getMediaConfig') - ->willReturn($mediaConfigMock); - - //verify new entries - $contentDataObject = $this->getMockBuilder(\Magento\Framework\Api\ImageContent::class) - ->disableOriginalConstructor() - ->setMethods(null) - ->getMock(); - $this->contentFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($contentDataObject); - - $this->imageProcessorMock->expects($this->once()) - ->method('processImageContent') - ->willReturn($absolutePath); - - $imageFileUri = "imageFileUri"; - $this->mediaGalleryProcessor->expects($this->once())->method('addImage') - ->with($this->initializedProductMock, $mediaTmpPath . $absolutePath, ['image', 'small_image'], true, false) - ->willReturn($imageFileUri); - $this->mediaGalleryProcessor->expects($this->once())->method('updateImage') - ->with( - $this->initializedProductMock, - $imageFileUri, - [ - 'label' => 'label_text', - 'position' => 10, - 'disabled' => false, - 'media_type' => 'media_type', - ] - ); + $this->mediaGalleryProcessor->expects($this->once())->method('processMediaGallery') + ->with($this->initializedProductMock, $expectedEntriesData); $this->initializedProductMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); $this->initializedProductMock->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); @@ -1325,24 +1292,6 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase ], ], ]; - - $expectedResult = [ - [ - 'value_id' => 5, - 'id' => 5, - "label" => "new_label_text", - 'file' => 'filename1', - 'position' => 10, - 'disabled' => false, - 'types' => ['image', 'small_image'], - ], - [ - 'value_id' => 6, //will be deleted - 'file' => 'filename2', - 'removed' => true, - ], - ]; - $this->setupProductMocksForSave(); //media gallery data $this->productData['media_gallery_entries'] = $newEntries; @@ -1352,21 +1301,15 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase ->will($this->returnValue($this->productData)); $this->initializedProductMock->setData('media_gallery', $existingMediaGallery); - $this->initializedProductMock->expects($this->any()) - ->method('getMediaAttributes') - ->willReturn(["image" => "filename1", "small_image" => "filename2"]); - $this->mediaGalleryProcessor->expects($this->once())->method('clearMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image']); $this->mediaGalleryProcessor->expects($this->once()) - ->method('setMediaAttribute') - ->with($this->initializedProductMock, ['image', 'small_image'], 'filename1'); + ->method('processMediaGallery') + ->with($this->initializedProductMock, $newEntries); $this->initializedProductMock->expects($this->once())->method('getWebsiteIds')->willReturn([]); $this->initializedProductMock->expects($this->atLeastOnce()) ->method('getSku')->willReturn($this->productData['sku']); $this->productMock->expects($this->atLeastOnce())->method('getSku')->willReturn($this->productData['sku']); $this->productMock->expects($this->any())->method('getMediaGalleryEntries')->willReturn(null); $this->model->save($this->productMock); - $this->assertEquals($expectedResult, $this->initializedProductMock->getMediaGallery('images')); } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/AbstractTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/AbstractTest.php index 64416301faa065bd4b4e1b003b855b70c5bbe0b3..96336d2b0706abd590d87cb82ff61a1a85c01e2c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/AbstractTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/AbstractTest.php @@ -44,11 +44,13 @@ class AbstractTest extends \PHPUnit\Framework\TestCase $code = 'test_attr'; $set = 10; + $storeId = 100; $object = $this->createPartialMock(\Magento\Catalog\Model\Product::class, ['__wakeup']); $object->setData('test_attr', 'test_attr'); $object->setData('attribute_set_id', $set); + $object->setData('store_id', $storeId); $entityType = new \Magento\Framework\DataObject(); $entityType->setEntityTypeCode('test'); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php index 6b908d317aa5b815e36f608f376a8d970b9a7d75..cec862ee9661f0aac6ca7d23c9ca17a1338a8b7c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPriceTest.php @@ -84,7 +84,7 @@ class LinkedProductSelectBuilderByIndexPriceTest extends \PHPUnit\Framework\Test $select->expects($this->any())->method('from')->willReturnSelf(); $select->expects($this->any())->method('joinInner')->willReturnSelf(); $select->expects($this->any())->method('where')->willReturnSelf(); - $select->expects($this->once())->method('order')->willReturnSelf(); + $select->expects($this->exactly(2))->method('order')->willReturnSelf(); $select->expects($this->once())->method('limit')->willReturnSelf(); $this->resourceMock->expects($this->any())->method('getConnection')->willReturn($connection); $this->metadataPoolMock->expects($this->once())->method('getMetadata')->willReturn($metadata); diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php index aec6549f400fc5742eb0a64612572039f4a1a878..683a96133ad3005beeea1812744da6496bd84ae2 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Attributes.php @@ -182,6 +182,11 @@ class Attributes extends AbstractModifier . '.create_new_attribute_modal', 'actionName' => 'toggleModal', ], + [ + 'targetName' => 'product_form.product_form.add_attribute_modal' + . '.create_new_attribute_modal.product_attribute_add_form', + 'actionName' => 'destroyInserted' + ], [ 'targetName' => 'product_form.product_form.add_attribute_modal' diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml index 6ff0e193a774f4b1878a289518a971e698ea3347..f812a27f87ad936c7199b56ed67535204c97d537 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml @@ -57,7 +57,7 @@ $stores = $block->getStoresSortedBySortOrder(); <input type="hidden" id="option-count-check" value="" /> </div> <script id="row-template" type="text/x-magento-template"> - <tr> + <tr <% if (data.rowClasses) { %>class="<%- data.rowClasses %>"<% } %>> <td class="col-draggable"> <?php if (!$block->getReadOnly() && !$block->canManageOptionDefaultOnly()): ?> <div data-role="draggable-handle" class="draggable-handle" diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml index 772bc1e6ec5d721b45a48b2ab1cfade000af6b09..f795fcabe701c0298e8362db57a0af372c0535e9 100644 --- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml +++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_attribute_add_form.xml @@ -147,7 +147,6 @@ <item name="isTemplate" xsi:type="boolean">true</item> <item name="is_collection" xsi:type="boolean">true</item> <item name="componentType" xsi:type="string">container</item> - <item name="positionProvider" xsi:type="string">attribute_options.position</item> </item> </argument> <field name="is_default" component="Magento_Catalog/js/form/element/checkbox" sortOrder="0" formElement="checkbox"> @@ -184,12 +183,8 @@ </item> </argument> <settings> - <additionalClasses> - <class name="_hidden">true</class> - </additionalClasses> <dataType>text</dataType> <visible>false</visible> - <dataScope>position</dataScope> </settings> </field> </container> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js index 51ffeaea0fc0ccda677a9648ee3b501a2562b207..2f6703cc92eac405dfb0763b47f1e5f08bd1d60a 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/form/element/input.js @@ -54,9 +54,16 @@ define([ if (!_.isEmpty(this.suffixName) || _.isNumber(this.suffixName)) { suffixName = '.' + this.suffixName; } - this.dataScope = 'data.' + this.prefixName + '.' + this.elementName + suffixName; - this.links.value = this.provider + ':' + this.dataScope; + this.exportDataLink = 'data.' + this.prefixName + '.' + this.elementName + suffixName; + this.exports.value = this.provider + ':' + this.exportDataLink; + }, + + /** @inheritdoc */ + destroy: function () { + this._super(); + + this.source.remove(this.exportDataLink); }, /** diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/options.js b/app/code/Magento/Catalog/view/adminhtml/web/js/options.js index 5239eb207efca66a7e3566fa26d62b03fbffac6f..787516a9abf29fa499d7131a11bac552343d160e 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/options.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/options.js @@ -24,6 +24,7 @@ define([ totalItems: 0, rendered: 0, template: mageTemplate('#row-template'), + newOptionClass: 'new-option', isReadOnly: config.isReadOnly, add: function (data, render) { var isNewOption = false, @@ -32,7 +33,8 @@ define([ if (typeof data.id == 'undefined') { data = { 'id': 'option_' + this.itemCount, - 'sort_order': this.itemCount + 1 + 'sort_order': this.itemCount + 1, + 'rowClasses': this.newOptionClass }; isNewOption = true; } @@ -84,6 +86,10 @@ define([ this.totalItems--; this.updateItemsCountField(); } + + if (element.hasClassName(this.newOptionClass)) { + element.remove(); + } }, updateItemsCountField: function () { $('option-count-check').value = this.totalItems > 0 ? '1' : ''; diff --git a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html index d4cfb02611416ae32b7e142e523058288245c9d5..9a52dcefa304281e71099b9d29f51a7a335752f9 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html +++ b/app/code/Magento/Catalog/view/adminhtml/web/template/form/element/action-delete.html @@ -7,7 +7,7 @@ <button class="action-delete" attr="{'data-action': 'remove_row'}" data-bind=" - click: function(){ $data.processingDeleteRecord($parents); }, + click: function(){ $parent.processingDeleteRecord($record().index, $record.recordId); }, attr: { title: $parent.deleteButtonLabel } diff --git a/app/code/Magento/Catalog/view/base/web/template/product/link.html b/app/code/Magento/Catalog/view/base/web/template/product/link.html index 98255e1b8a9e2a88aaad78ff9081818f8cb34161..2c70300f7aec3151e8476050338f07054e62db05 100644 --- a/app/code/Magento/Catalog/view/base/web/template/product/link.html +++ b/app/code/Magento/Catalog/view/base/web/template/product/link.html @@ -4,6 +4,7 @@ * See COPYING.txt for license details. */ --> -<a class="product-item-link" +<a if="isAllowed()" + class="product-item-link" attr="href: $row().url" text="label"/> diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml index fc0967ca60d2d0c0701a74ecd421585bc09f38fc..862375503691caba52f6c33432f9210ffcf2e06e 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml @@ -146,8 +146,8 @@ switch ($type = $block->getType()) { } break; - case 'other': - break; + default: + $exist = null; } ?> diff --git a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js index 7686de1d45c5dad276775c7ae624db0b6cac8365..eb54d8af001b3b338080cfa0d71f4478535bd330 100644 --- a/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js +++ b/app/code/Magento/Catalog/view/frontend/web/js/catalog-add-to-cart.js @@ -97,7 +97,7 @@ define([ success: function (res) { var eventData, parameters; - $(document).trigger('ajax:addToCart', form.data().productSku); + $(document).trigger('ajax:addToCart', form.data().productSku, form, res); if (self.isLoaderEnabled()) { $('body').trigger(self.options.processStop); diff --git a/app/code/Magento/CatalogInventory/etc/events.xml b/app/code/Magento/CatalogInventory/etc/events.xml index 0a9f3c2d40dcad6495d77ef2269f20b72d48da4e..3197501e9b70b1f410f4765c2282276dfceed924 100644 --- a/app/code/Magento/CatalogInventory/etc/events.xml +++ b/app/code/Magento/CatalogInventory/etc/events.xml @@ -27,9 +27,6 @@ <event name="sales_model_service_quote_submit_failure"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\RevertQuoteInventoryObserver"/> </event> - <event name="restore_quote"> - <observer name="inventory" instance="Magento\CatalogInventory\Observer\RevertQuoteInventoryObserver"/> - </event> <event name="sales_order_item_cancel"> <observer name="inventory" instance="Magento\CatalogInventory\Observer\CancelOrderItemObserver"/> </event> diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php index 00808f38c913263d2dc2ddfade74416069f4326f..3f396cacd37dab442e332343586d6f27c880b72f 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php @@ -105,6 +105,7 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.rule_date = ?', $currentDate) ->order('t.rule_price ' . Select::SQL_ASC) + ->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC) ->limit(1); $priceSelect = $this->baseSelectProcessor->process($priceSelect); diff --git a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml index 201d6ffe4c68344608f274589dd647f69a336d9f..574cbe1107e883b110fff30fcc384b8415d0b205 100644 --- a/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml +++ b/app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml @@ -35,8 +35,7 @@ <ol class="product-items <?= /* @noEscape */ $type ?>"> <?php $iterator = 1; ?> <?php foreach ($items as $_item): ?> - <?php if ($iterator++ != 1): ?></li><?php endif ?> - <li class="product-item"> + <?= /* @noEscape */ ($iterator++ == 1) ? '<li class="product-item">' : '</li><li class="product-item">' ?> <div class="product-item-info"> <a href="<?= $block->escapeUrl($block->getProductUrl($_item)) ?>" class="product-item-photo"> <?= $block->getImage($_item, $image)->toHtml() ?> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml index b224c96f07e9bb42c1c24a4bc8a752821e15b3ec..1d67b325e01c5d07e709144cdc0fec7745f94e1d 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/coupon.phtml @@ -24,7 +24,7 @@ <div class="field"> <label for="coupon_code" class="label"><span><?= /* @escapeNotVerified */ __('Enter discount code') ?></span></label> <div class="control"> - <input type="text" class="input-text" id="coupon_code" name="coupon_code" value="<?= $block->escapeHtml($block->getCouponCode()) ?>" placeholder="<?= $block->escapeHtml(__('Enter discount code')) ?>" /> + <input type="text" class="input-text" id="coupon_code" name="coupon_code" value="<?= $block->escapeHtml($block->getCouponCode()) ?>" placeholder="<?= $block->escapeHtml(__('Enter discount code')) ?>" <?php if (strlen($block->getCouponCode())): ?> disabled="disabled" <?php endif; ?> /> </div> </div> <div class="actions-toolbar"> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/payload-extender.js b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/payload-extender.js index 9a082a056a3824545ee7afc5fc361255e3f6d62c..dcd4340774af473dba75f29d113f3698c53c7ec1 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/payload-extender.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-save-processor/payload-extender.js @@ -1,5 +1,5 @@ /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define(function () { diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html index f2baf5d50030e80f34dc7dda2d32a700ea6a8124..fd994a4e8a9553b9f77f251428a78e9e05faa50b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html @@ -7,7 +7,7 @@ <div class="billing-address-details" data-bind="if: isAddressDetailsVisible() && currentBillingAddress()"> <!-- ko text: currentBillingAddress().prefix --><!-- /ko --> <!-- ko text: currentBillingAddress().firstname --><!-- /ko --> <!-- ko text: currentBillingAddress().middlename --><!-- /ko --> <!-- ko text: currentBillingAddress().lastname --><!-- /ko --> <!-- ko text: currentBillingAddress().suffix --><!-- /ko --><br/> - <!-- ko text: currentBillingAddress().street --><!-- /ko --><br/> + <!-- ko text: _.values(currentBillingAddress().street).join(", ") --><!-- /ko --><br/> <!-- ko text: currentBillingAddress().city --><!-- /ko -->, <span data-bind="html: currentBillingAddress().region"></span> <!-- ko text: currentBillingAddress().postcode --><!-- /ko --><br/> <!-- ko text: getCountryName(currentBillingAddress().countryId) --><!-- /ko --><br/> <!-- ko if: (currentBillingAddress().telephone) --> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html index ec41cae0bdc5e17cc3920b669f7c8337a1fc9dac..b66526f660af761d974c9bc61857a60c6e66466f 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html @@ -7,7 +7,7 @@ <!-- ko if: (visible()) --> <!-- ko text: address().prefix --><!-- /ko --> <!-- ko text: address().firstname --><!-- /ko --> <!-- ko text: address().middlename --><!-- /ko --> <!-- ko text: address().lastname --><!-- /ko --> <!-- ko text: address().suffix --><!-- /ko --><br/> - <!-- ko text: address().street --><!-- /ko --><br/> + <!-- ko text: _.values(address().street).join(", ") --><!-- /ko --><br/> <!-- ko text: address().city --><!-- /ko -->, <span data-bind="html: address().region"></span> <!-- ko text: address().postcode --><!-- /ko --><br/> <!-- ko text: getCountryName(address().countryId) --><!-- /ko --><br/> <!-- ko if: (address().telephone) --> diff --git a/app/code/Magento/Cms/Api/GetUtilityPageIdentifiersInterface.php b/app/code/Magento/Cms/Api/GetUtilityPageIdentifiersInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c6bf4c840470125319d5f07da23ad6529e4c1201 --- /dev/null +++ b/app/code/Magento/Cms/Api/GetUtilityPageIdentifiersInterface.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cms\Api; + +/** + * Utility Cms Pages + * + * @api + */ +interface GetUtilityPageIdentifiersInterface +{ + /** + * Get List Page Identifiers + * @return array + */ + public function execute(); +} diff --git a/app/code/Magento/Cms/Model/Block.php b/app/code/Magento/Cms/Model/Block.php index fc369971054d7281b2bccf72d48cf477d8b2090c..3795409a95d2b49f2ae0efc64f30abf050f14062 100644 --- a/app/code/Magento/Cms/Model/Block.php +++ b/app/code/Magento/Cms/Model/Block.php @@ -6,7 +6,6 @@ namespace Magento\Cms\Model; use Magento\Cms\Api\Data\BlockInterface; -use Magento\Cms\Model\ResourceModel\Block as ResourceCmsBlock; use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Model\AbstractModel; @@ -58,6 +57,11 @@ class Block extends AbstractModel implements BlockInterface, IdentityInterface public function beforeSave() { $needle = 'block_id="' . $this->getId() . '"'; + + if ($this->hasDataChanges()) { + $this->setUpdateTime(null); + } + if (false == strstr($this->getContent(), $needle)) { return parent::beforeSave(); } diff --git a/app/code/Magento/Cms/Model/GetUtilityPageIdentifiers.php b/app/code/Magento/Cms/Model/GetUtilityPageIdentifiers.php new file mode 100644 index 0000000000000000000000000000000000000000..09c68ee9cf82d7bd1851788f9863e04638ad0ff5 --- /dev/null +++ b/app/code/Magento/Cms/Model/GetUtilityPageIdentifiers.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cms\Model; + +use Magento\Cms\Api\GetUtilityPageIdentifiersInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Store\Model\ScopeInterface; + +/** + * Utility Cms Pages + */ +class GetUtilityPageIdentifiers implements GetUtilityPageIdentifiersInterface +{ + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * UtilityCmsPage constructor. + * @param ScopeConfigInterface $scopeConfig + */ + public function __construct( + ScopeConfigInterface $scopeConfig + ) { + $this->scopeConfig = $scopeConfig; + } + + /** + * Get List Page Identifiers + * @return array + */ + public function execute() + { + $homePageIdentifier = $this->scopeConfig->getValue( + 'web/default/cms_home_page', + ScopeInterface::SCOPE_STORE + ); + $noRouteIdentifier = $this->scopeConfig->getValue( + 'web/default/cms_no_route', + ScopeInterface::SCOPE_STORE + ); + + $noCookieIdentifier = $this->scopeConfig->getValue( + 'web/default/cms_no_cookies', + ScopeInterface::SCOPE_STORE + ); + + return [$homePageIdentifier, $noRouteIdentifier, $noCookieIdentifier]; + } +} diff --git a/app/code/Magento/Cms/Model/Page.php b/app/code/Magento/Cms/Model/Page.php index 591f8d93fcdc6cee0a2e9f5bbc82fe871701ae18..d950f484cd1d99c27d3d1d2f796c094e96906672 100644 --- a/app/code/Magento/Cms/Model/Page.php +++ b/app/code/Magento/Cms/Model/Page.php @@ -6,12 +6,11 @@ namespace Magento\Cms\Model; use Magento\Cms\Api\Data\PageInterface; -use Magento\Cms\Model\ResourceModel\Page as ResourceCmsPage; +use Magento\Cms\Helper\Page as PageHelper; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\DataObject\IdentityInterface; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\AbstractModel; -use Magento\Cms\Helper\Page as PageHelper; /** * Cms Page Model @@ -547,6 +546,10 @@ class Page extends AbstractModel implements PageInterface, IdentityInterface $originalIdentifier = $this->getOrigData('identifier'); $currentIdentifier = $this->getIdentifier(); + if ($this->hasDataChanges()) { + $this->setUpdateTime(null); + } + if (!$this->getId() || $originalIdentifier === $currentIdentifier) { return parent::beforeSave(); } diff --git a/app/code/Magento/Cms/Model/ResourceModel/Block.php b/app/code/Magento/Cms/Model/ResourceModel/Block.php index d5bae7359fe356892b2ff5a6288538c8dee6a683..9aab54b02bc14fcae402b5e30570e1b2c28b5699 100644 --- a/app/code/Magento/Cms/Model/ResourceModel/Block.php +++ b/app/code/Magento/Cms/Model/ResourceModel/Block.php @@ -7,10 +7,10 @@ namespace Magento\Cms\Model\ResourceModel; use Magento\Cms\Api\Data\BlockInterface; use Magento\Framework\DB\Select; +use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\AbstractModel; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\EntityManager\EntityManager; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; use Magento\Framework\Model\ResourceModel\Db\Context; use Magento\Store\Model\Store; diff --git a/app/code/Magento/Cms/Model/ResourceModel/Page.php b/app/code/Magento/Cms/Model/ResourceModel/Page.php index 8e26c8b67fa4b58cace3e3bbfb62f6d95796bef4..b836cf199632d34e345c732cfd06f645da566e3e 100644 --- a/app/code/Magento/Cms/Model/ResourceModel/Page.php +++ b/app/code/Magento/Cms/Model/ResourceModel/Page.php @@ -6,18 +6,18 @@ namespace Magento\Cms\Model\ResourceModel; +use Magento\Cms\Api\Data\PageInterface; use Magento\Cms\Model\Page as CmsPage; use Magento\Framework\DB\Select; +use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Model\AbstractModel; -use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Model\ResourceModel\Db\AbstractDb; use Magento\Framework\Model\ResourceModel\Db\Context; use Magento\Framework\Stdlib\DateTime; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\EntityManager\EntityManager; -use Magento\Cms\Api\Data\PageInterface; /** * Cms page mysql resource diff --git a/app/code/Magento/Cms/etc/di.xml b/app/code/Magento/Cms/etc/di.xml index 8309e3b5b615042999daf62acdf7084b884a9ac8..d262ebca1591c6ef90e8079dac96660247de1b97 100644 --- a/app/code/Magento/Cms/etc/di.xml +++ b/app/code/Magento/Cms/etc/di.xml @@ -14,6 +14,7 @@ <preference for="Magento\Cms\Api\Data\BlockInterface" type="Magento\Cms\Model\Block" /> <preference for="Magento\Cms\Api\BlockRepositoryInterface" type="Magento\Cms\Model\BlockRepository" /> <preference for="Magento\Cms\Api\PageRepositoryInterface" type="Magento\Cms\Model\PageRepository" /> + <preference for="Magento\Cms\Api\GetUtilityPageIdentifiersInterface" type="Magento\Cms\Model\GetUtilityPageIdentifiers" /> <type name="Magento\Cms\Model\Wysiwyg\Config"> <arguments> <argument name="windowSize" xsi:type="array"> diff --git a/app/code/Magento/Config/Model/Config/Source/Nooptreq.php b/app/code/Magento/Config/Model/Config/Source/Nooptreq.php index 03fe5ca2abcccfe62721f747e3f7d3d4f314599f..1c9eb801dfec78962b71b9f9358ad6a462087dab 100644 --- a/app/code/Magento/Config/Model/Config/Source/Nooptreq.php +++ b/app/code/Magento/Config/Model/Config/Source/Nooptreq.php @@ -11,15 +11,19 @@ namespace Magento\Config\Model\Config\Source; */ class Nooptreq implements \Magento\Framework\Option\ArrayInterface { + const VALUE_NO = ''; + const VALUE_OPTIONAL = 'opt'; + const VALUE_REQUIRED = 'req'; + /** * @return array */ public function toOptionArray() { return [ - ['value' => '', 'label' => __('No')], - ['value' => 'opt', 'label' => __('Optional')], - ['value' => 'req', 'label' => __('Required')] + ['value' => self::VALUE_NO, 'label' => __('No')], + ['value' => self::VALUE_OPTIONAL, 'label' => __('Optional')], + ['value' => self::VALUE_REQUIRED, 'label' => __('Required')] ]; } } diff --git a/app/code/Magento/Config/Model/Config/Structure/Reader.php b/app/code/Magento/Config/Model/Config/Structure/Reader.php index 5916649588bcb271c25ba13d368e62e22602b345..c83c2e1ae13207d29ecea5d51daf46ee99ed82d8 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Reader.php +++ b/app/code/Magento/Config/Model/Config/Structure/Reader.php @@ -124,6 +124,7 @@ class Reader extends \Magento\Framework\Config\Reader\Filesystem * Processing nodes of the document before merging * * @param string $content + * @throws \Magento\Framework\Config\Dom\ValidationException * @return string */ protected function processingDocument($content) @@ -131,7 +132,12 @@ class Reader extends \Magento\Framework\Config\Reader\Filesystem $object = new DataObject(); $document = new \DOMDocument(); - $document->loadXML($content); + try { + $document->loadXML($content); + } catch (\Exception $e) { + throw new \Magento\Framework\Config\Dom\ValidationException($e->getMessage()); + } + $this->compiler->compile($document->documentElement, $object, $object); return $document->saveXML(); diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php index 958d802682d525097a2bc80db8e9ff9093c4c58e..5d9eed0a188fca900c210e3049295811d95272ff 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Attribute/OptionSelectBuilder.php @@ -91,6 +91,12 @@ class OptionSelectBuilder implements OptionSelectBuilderInterface ] ), [] + )->joinInner( + ['attribute_option' => $this->attributeResource->getTable('eav_attribute_option')], + 'attribute_option.option_id = entity_value.value', + [] + )->order( + 'attribute_option.sort_order ASC' )->where( 'super_attribute.product_id = ?', $productId diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index a30ec81528dd38691e65bafd417aa59ae32ab3d7..40f390d5269749266c57901e991c80aaacaf295c 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -58,7 +58,7 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\ if ($this->hasEntity() || !empty($entityIds)) { $this->prepareFinalPriceDataForType($entityIds, $this->getTypeId()); $this->_applyCustomOption(); - $this->_applyConfigurableOption(); + $this->_applyConfigurableOption($entityIds); $this->_movePriceDataToIndexTable($entityIds); } return $this; @@ -110,10 +110,11 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\ * Calculate minimal and maximal prices for configurable product options * and apply it to final price * + * @param null|int|array $entityIds * @return \Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\Configurable * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - protected function _applyConfigurableOption() + protected function _applyConfigurableOption($entityIds = null) { $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); $connection = $this->getConnection(); @@ -133,12 +134,12 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\ ['le' => $this->getTable('catalog_product_entity')], 'le.' . $linkField . ' = l.parent_id', ['parent_id' => 'entity_id'] - )->join( - ['i' => $this->_getDefaultFinalPriceTable()], - 'le.entity_id = i.entity_id', - [] ); + if ($entityIds !== null) { + $subSelect->where('le.entity_id IN (?)', $entityIds); + } + $select = $connection->select(); $select ->from(['sub' => new \Zend_Db_Expr('(' . (string)$subSelect . ')')], '') @@ -178,6 +179,7 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\ ' AND i.website_id = io.website_id', [] ); + // adds price of custom option, that was applied in DefaultPrice::_applyCustomOption $select->columns( [ 'min_price' => new \Zend_Db_Expr('i.min_price - i.orig_price + io.min_price'), diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php index 235c16c9b556c58961931a98ffecf9333408de30..9802c97372bbbb4fcff3e6837ff05a116dd530fb 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/ResourceModel/Attribute/OptionSelectBuilderTest.php @@ -66,7 +66,7 @@ class OptionSelectBuilderTest extends \PHPUnit\Framework\TestCase ->disableOriginalConstructor() ->getMockForAbstractClass(); $this->select = $this->getMockBuilder(Select::class) - ->setMethods(['from', 'joinInner', 'joinLeft', 'where', 'columns']) + ->setMethods(['from', 'joinInner', 'joinLeft', 'where', 'columns', 'order']) ->disableOriginalConstructor() ->getMock(); $this->connectionMock->expects($this->atLeastOnce()) @@ -112,10 +112,28 @@ class OptionSelectBuilderTest extends \PHPUnit\Framework\TestCase { $this->select->expects($this->exactly(1))->method('from')->willReturnSelf(); $this->select->expects($this->exactly(1))->method('columns')->willReturnSelf(); - $this->select->expects($this->exactly(5))->method('joinInner')->willReturnSelf(); + $this->select->expects($this->exactly(6))->method('joinInner')->willReturnSelf(); $this->select->expects($this->exactly(3))->method('joinLeft')->willReturnSelf(); + $this->select->expects($this->exactly(1))->method('order')->willReturnSelf(); $this->select->expects($this->exactly(2))->method('where')->willReturnSelf(); + $this->attributeResourceMock->expects($this->exactly(9)) + ->method('getTable') + ->will( + $this->returnValueMap( + [ + ['catalog_product_super_attribute', 'catalog_product_super_attribute value'], + ['catalog_product_entity', 'catalog_product_entity value'], + ['catalog_product_super_link', 'catalog_product_super_link value'], + ['eav_attribute', 'eav_attribute value'], + ['catalog_product_entity', 'catalog_product_entity value'], + ['catalog_product_super_attribute_label', 'catalog_product_super_attribute_label value'], + ['eav_attribute_option', 'eav_attribute_option value'], + ['eav_attribute_option_value', 'eav_attribute_option_value value'] + ] + ) + ); + $this->abstractAttributeMock->expects($this->atLeastOnce()) ->method('getAttributeId') ->willReturn('getAttributeId value'); @@ -138,10 +156,27 @@ class OptionSelectBuilderTest extends \PHPUnit\Framework\TestCase { $this->select->expects($this->exactly(1))->method('from')->willReturnSelf(); $this->select->expects($this->exactly(0))->method('columns')->willReturnSelf(); - $this->select->expects($this->exactly(5))->method('joinInner')->willReturnSelf(); + $this->select->expects($this->exactly(6))->method('joinInner')->willReturnSelf(); $this->select->expects($this->exactly(1))->method('joinLeft')->willReturnSelf(); + $this->select->expects($this->exactly(1))->method('order')->willReturnSelf(); $this->select->expects($this->exactly(2))->method('where')->willReturnSelf(); + $this->attributeResourceMock->expects($this->exactly(7)) + ->method('getTable') + ->will( + $this->returnValueMap( + [ + ['catalog_product_super_attribute', 'catalog_product_super_attribute value'], + ['catalog_product_entity', 'catalog_product_entity value'], + ['catalog_product_super_link', 'catalog_product_super_link value'], + ['eav_attribute', 'eav_attribute value'], + ['catalog_product_entity', 'catalog_product_entity value'], + ['catalog_product_super_attribute_label', 'catalog_product_super_attribute_label value'], + ['eav_attribute_option', 'eav_attribute_option value'] + ] + ) + ); + $this->abstractAttributeMock->expects($this->atLeastOnce()) ->method('getAttributeId') ->willReturn('getAttributeId value'); diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php index 0e03dfe3cde51e27d00e58a057971a0d5de04a51..9fd225e8acaab0ad561e8d2d80817082f68d0d58 100644 --- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php +++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php @@ -5,6 +5,7 @@ */ namespace Magento\ConfigurableProduct\Ui\DataProvider\Product\Form\Modifier; +use Magento\Catalog\Model\Product\Attribute\Backend\Sku; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Ui\Component\Container; use Magento\Ui\Component\Form; @@ -466,7 +467,17 @@ class ConfigurablePanel extends AbstractModifier [], ['dataScope' => 'product_link'] ), - 'sku_container' => $this->getColumn('sku', __('SKU')), + 'sku_container' => $this->getColumn( + 'sku', + __('SKU'), + [ + 'validation' => + [ + 'required-entry' => true, + 'max_text_length' => Sku::SKU_MAX_LENGTH, + ] + ] + ), 'price_container' => $this->getColumn( 'price', __('Price'), diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js index 24fc24363562b94fcae0cb8684edcf2e6a09f353..be290e49a43c372bf8a2dc2a4d0a53127f9cd2f6 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/variations/variations.js @@ -357,12 +357,12 @@ define([ var element; _.each(this.disabledAttributes, function (attribute) { - registry.get('index = ' + attribute).disabled(false); + registry.get('code = ' + attribute, 'index = ' + attribute).disabled(false); }); this.disabledAttributes = []; _.each(attributes, function (attribute) { - element = registry.get('index = ' + attribute.code); + element = registry.get('code = ' + attribute.code, 'index = ' + attribute.code); if (!_.isUndefined(element)) { element.disabled(true); diff --git a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php index dceb5767edae91ce12db919370c0ea0a38c33d0d..42d7d91fb90e85785acdb3f6a53f1d37f6e680a6 100644 --- a/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php +++ b/app/code/Magento/ConfigurableProductSales/Model/Order/Reorder/OrderedProductAvailabilityChecker.php @@ -45,7 +45,7 @@ class OrderedProductAvailabilityChecker implements OrderedProductAvailabilityChe public function isAvailable(Item $item) { $buyRequest = $item->getBuyRequest(); - $superAttribute = $buyRequest->getData()['super_attribute']; + $superAttribute = $buyRequest->getData()['super_attribute'] ?? []; $connection = $this->getConnection(); $select = $connection->select(); $orderItemParentId = $item->getParentItem()->getProductId(); diff --git a/app/code/Magento/Customer/Block/Widget/Name.php b/app/code/Magento/Customer/Block/Widget/Name.php index ecd09319cd85ec47fd3ef0f2ac81ee683fe9491a..35f3bbefb8f00ee9a3d2cb2b7b6e3fb82bbe12ee 100644 --- a/app/code/Magento/Customer/Block/Widget/Name.php +++ b/app/code/Magento/Customer/Block/Widget/Name.php @@ -106,8 +106,11 @@ class Name extends AbstractWidget $prefixOptions = $this->options->getNamePrefixOptions(); if ($this->getObject() && !empty($prefixOptions)) { - $oldPrefix = $this->escapeHtml(trim($this->getObject()->getPrefix())); - $prefixOptions[$oldPrefix] = $oldPrefix; + $prefixOption = $this->getObject()->getPrefix(); + $oldPrefix = $this->escapeHtml(trim($prefixOption)); + if ($prefixOption !== null && !isset($prefixOptions[$oldPrefix]) && !isset($prefixOptions[$prefixOption])) { + $prefixOptions[$oldPrefix] = $oldPrefix; + } } return $prefixOptions; } @@ -161,8 +164,11 @@ class Name extends AbstractWidget { $suffixOptions = $this->options->getNameSuffixOptions(); if ($this->getObject() && !empty($suffixOptions)) { - $oldSuffix = $this->escapeHtml(trim($this->getObject()->getSuffix())); - $suffixOptions[$oldSuffix] = $oldSuffix; + $suffixOption = $this->getObject()->getSuffix(); + $oldSuffix = $this->escapeHtml(trim($suffixOption)); + if ($suffixOption !== null && !isset($suffixOptions[$oldSuffix]) && !isset($suffixOptions[$suffixOption])) { + $suffixOptions[$oldSuffix] = $oldSuffix; + } } return $suffixOptions; } diff --git a/app/code/Magento/Customer/Controller/Account/Confirmation.php b/app/code/Magento/Customer/Controller/Account/Confirmation.php index e7d23cac8d62a6b0dfd8e777fe22ccec8a1980f1..a3e2db0207630d7ecdea0e3d78eef99e70ed1ebb 100644 --- a/app/code/Magento/Customer/Controller/Account/Confirmation.php +++ b/app/code/Magento/Customer/Controller/Account/Confirmation.php @@ -6,8 +6,10 @@ */ namespace Magento\Customer\Controller\Account; +use Magento\Customer\Model\Url; use Magento\Framework\App\Action\Context; use Magento\Customer\Model\Session; +use Magento\Framework\App\ObjectManager; use Magento\Framework\View\Result\PageFactory; use Magento\Store\Model\StoreManagerInterface; use Magento\Customer\Api\AccountManagementInterface; @@ -35,24 +37,32 @@ class Confirmation extends \Magento\Customer\Controller\AbstractAccount */ protected $resultPageFactory; + /** + * @var Url + */ + private $customerUrl; + /** * @param Context $context * @param Session $customerSession * @param PageFactory $resultPageFactory * @param StoreManagerInterface $storeManager * @param AccountManagementInterface $customerAccountManagement + * @param Url $customerUrl */ public function __construct( Context $context, Session $customerSession, PageFactory $resultPageFactory, StoreManagerInterface $storeManager, - AccountManagementInterface $customerAccountManagement + AccountManagementInterface $customerAccountManagement, + Url $customerUrl = null ) { $this->session = $customerSession; $this->resultPageFactory = $resultPageFactory; $this->storeManager = $storeManager; $this->customerAccountManagement = $customerAccountManagement; + $this->customerUrl = $customerUrl ?: ObjectManager::getInstance()->get(Url::class); parent::__construct($context); } @@ -98,6 +108,8 @@ class Confirmation extends \Magento\Customer\Controller\AbstractAccount $resultPage = $this->resultPageFactory->create(); $resultPage->getLayout()->getBlock('accountConfirmation')->setEmail( $this->getRequest()->getParam('email', $email) + )->setLoginUrl( + $this->customerUrl->getLoginUrl() ); return $resultPage; } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index 44eba83d96d7e44e3a7fc95dcd80398f8210a309..12732f81f78a0cb2b20628861d0023377b79fdc5 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -13,6 +13,9 @@ use Magento\Customer\Model\EmailNotificationInterface; use Magento\Customer\Model\Metadata\Form; use Magento\Framework\Exception\LocalizedException; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Save extends \Magento\Customer\Controller\Adminhtml\Index { /** @@ -268,6 +271,15 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index $this->_addSessionErrorMessages($messages); $this->_getSession()->setCustomerFormData($originalRequestData); $returnToEdit = true; + } catch (\Magento\Framework\Exception\AbstractAggregateException $exception) { + $errors = $exception->getErrors(); + $messages = []; + foreach ($errors as $error) { + $messages[] = $error->getMessage(); + } + $this->_addSessionErrorMessages($messages); + $this->_getSession()->setCustomerFormData($originalRequestData); + $returnToEdit = true; } catch (LocalizedException $exception) { $this->_addSessionErrorMessages($exception->getMessage()); $this->_getSession()->setCustomerFormData($originalRequestData); diff --git a/app/code/Magento/Customer/Model/AccountConfirmation.php b/app/code/Magento/Customer/Model/AccountConfirmation.php new file mode 100644 index 0000000000000000000000000000000000000000..7d01ff0efc4117aa26d71b85c1f8226d7178d8f2 --- /dev/null +++ b/app/code/Magento/Customer/Model/AccountConfirmation.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Model; + +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Registry; + +/** + * Class AccountConfirmation. + * Checks if email confirmation required for customer. + */ +class AccountConfirmation +{ + /** + * Configuration path for email confirmation. + */ + const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm'; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var Registry + */ + private $registry; + + /** + * @param ScopeConfigInterface $scopeConfig + * @param Registry $registry + */ + public function __construct( + ScopeConfigInterface $scopeConfig, + Registry $registry + ) { + $this->scopeConfig = $scopeConfig; + $this->registry = $registry; + } + + /** + * Check if accounts confirmation is required. + * + * @param int|null $websiteId + * @param int|null $customerId + * @param string $customerEmail + * @return bool + */ + public function isConfirmationRequired($websiteId, $customerId, $customerEmail): bool + { + if ($this->canSkipConfirmation($customerId, $customerEmail)) { + return false; + } + + return (bool)$this->scopeConfig->getValue( + self::XML_PATH_IS_CONFIRM, + ScopeInterface::SCOPE_WEBSITES, + $websiteId + ); + } + + /** + * Check whether confirmation may be skipped when registering using certain email address. + * + * @param int|null $customerId + * @param string $customerEmail + * @return bool + */ + private function canSkipConfirmation($customerId, $customerEmail): bool + { + if (!$customerId) { + return false; + } + + /* If an email was used to start the registration process and it is the same email as the one + used to register, then this can skip confirmation. + */ + $skipConfirmationIfEmail = $this->registry->registry("skip_confirmation_if_email"); + if (!$skipConfirmationIfEmail) { + return false; + } + + return strtolower($skipConfirmationIfEmail) === strtolower($customerEmail); + } +} diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 894dd5931a63c6be9e8590cc422aa2d0de92b666..8ba44f86f5a0ee68f6c9d26339a4af1dcd24a5a1 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -89,6 +89,10 @@ class AccountManagement implements AccountManagementInterface */ const XML_PATH_FORGOT_EMAIL_IDENTITY = 'customer/password/forgot_email_identity'; + /** + * @deprecated + * @see AccountConfirmation::XML_PATH_IS_CONFIRM + */ const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm'; /** @@ -298,6 +302,11 @@ class AccountManagement implements AccountManagementInterface */ private $dateTimeFactory; + /** + * @var AccountConfirmation + */ + private $accountConfirmation; + /** * @param CustomerFactory $customerFactory * @param ManagerInterface $eventManager @@ -323,7 +332,8 @@ class AccountManagement implements AccountManagementInterface * @param ObjectFactory $objectFactory * @param ExtensibleDataObjectConverter $extensibleDataObjectConverter * @param CredentialsValidator|null $credentialsValidator - * @param DateTimeFactory $dateTimeFactory + * @param DateTimeFactory|null $dateTimeFactory + * @param AccountConfirmation|null $accountConfirmation * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -351,7 +361,8 @@ class AccountManagement implements AccountManagementInterface ObjectFactory $objectFactory, ExtensibleDataObjectConverter $extensibleDataObjectConverter, CredentialsValidator $credentialsValidator = null, - DateTimeFactory $dateTimeFactory = null + DateTimeFactory $dateTimeFactory = null, + AccountConfirmation $accountConfirmation = null ) { $this->customerFactory = $customerFactory; $this->eventManager = $eventManager; @@ -379,6 +390,8 @@ class AccountManagement implements AccountManagementInterface $this->credentialsValidator = $credentialsValidator ?: ObjectManager::getInstance()->get(CredentialsValidator::class); $this->dateTimeFactory = $dateTimeFactory ?: ObjectManager::getInstance()->get(DateTimeFactory::class); + $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() + ->get(AccountConfirmation::class); } /** @@ -1149,17 +1162,15 @@ class AccountManagement implements AccountManagementInterface * * @param CustomerInterface $customer * @return bool + * @deprecated + * @see AccountConfirmation::isConfirmationRequired */ protected function isConfirmationRequired($customer) { - if ($this->canSkipConfirmation($customer)) { - return false; - } - - return (bool)$this->scopeConfig->getValue( - self::XML_PATH_IS_CONFIRM, - ScopeInterface::SCOPE_WEBSITES, - $customer->getWebsiteId() + return $this->accountConfirmation->isConfirmationRequired( + $customer->getWebsiteId(), + $customer->getId(), + $customer->getEmail() ); } @@ -1168,6 +1179,8 @@ class AccountManagement implements AccountManagementInterface * * @param CustomerInterface $customer * @return bool + * @deprecated + * @see AccountConfirmation::isConfirmationRequired */ protected function canSkipConfirmation($customer) { diff --git a/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php b/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php index a333fe8df594a97e239aefd65469fcf1b16f42aa..36eabe3571ceb267cb2cf4bafad1c7b0431ddcd9 100644 --- a/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php +++ b/app/code/Magento/Customer/Model/Checkout/ConfigProvider.php @@ -7,6 +7,7 @@ namespace Magento\Customer\Model\Checkout; use Magento\Checkout\Model\ConfigProviderInterface; use Magento\Customer\Model\Url; +use Magento\Framework\App\ObjectManager; use Magento\Framework\UrlInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\App\Config\ScopeConfigInterface; @@ -22,6 +23,7 @@ class ConfigProvider implements ConfigProviderInterface /** * @var UrlInterface + * @deprecated */ protected $urlBuilder; @@ -30,19 +32,28 @@ class ConfigProvider implements ConfigProviderInterface */ protected $scopeConfig; + /** + * @var Url + */ + private $customerUrl; + /** * @param UrlInterface $urlBuilder * @param StoreManagerInterface $storeManager * @param ScopeConfigInterface $scopeConfig + * @param Url|null $customerUrl */ public function __construct( UrlInterface $urlBuilder, StoreManagerInterface $storeManager, - ScopeConfigInterface $scopeConfig + ScopeConfigInterface $scopeConfig, + Url $customerUrl = null ) { $this->urlBuilder = $urlBuilder; $this->storeManager = $storeManager; $this->scopeConfig = $scopeConfig; + $this->customerUrl = $customerUrl ?? ObjectManager::getInstance() + ->get(Url::class); } /** @@ -78,7 +89,7 @@ class ConfigProvider implements ConfigProviderInterface */ protected function getLoginUrl() { - return $this->urlBuilder->getUrl(Url::ROUTE_ACCOUNT_LOGIN); + return $this->customerUrl->getLoginUrl(); } /** diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index 2e2260f16ff919bc08121ba8b89ffd6439a5270c..e0a7281776de929a9f394e7b5035724ee68071c2 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -18,6 +18,7 @@ use Magento\Framework\Exception\InvalidEmailOrPasswordException; use Magento\Framework\Indexer\StateInterface; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\ObjectManager; /** * Customer model @@ -58,6 +59,10 @@ class Customer extends \Magento\Framework\Model\AbstractModel const XML_PATH_RESET_PASSWORD_TEMPLATE = 'customer/password/reset_password_template'; + /** + * @deprecated + * @see AccountConfirmation::XML_PATH_IS_CONFIRM + */ const XML_PATH_IS_CONFIRM = 'customer/create_account/confirm'; const XML_PATH_CONFIRM_EMAIL_TEMPLATE = 'customer/create_account/email_confirmation_template'; @@ -208,6 +213,11 @@ class Customer extends \Magento\Framework\Model\AbstractModel */ protected $indexerRegistry; + /** + * @var AccountConfirmation + */ + private $accountConfirmation; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -229,6 +239,7 @@ class Customer extends \Magento\Framework\Model\AbstractModel * @param \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection * @param array $data + * @param AccountConfirmation|null $accountConfirmation * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -252,7 +263,8 @@ class Customer extends \Magento\Framework\Model\AbstractModel \Magento\Customer\Api\CustomerMetadataInterface $metadataService, \Magento\Framework\Indexer\IndexerRegistry $indexerRegistry, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + AccountConfirmation $accountConfirmation = null ) { $this->metadataService = $metadataService; $this->_scopeConfig = $scopeConfig; @@ -269,6 +281,8 @@ class Customer extends \Magento\Framework\Model\AbstractModel $this->dataObjectProcessor = $dataObjectProcessor; $this->dataObjectHelper = $dataObjectHelper; $this->indexerRegistry = $indexerRegistry; + $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() + ->get(AccountConfirmation::class); parent::__construct( $context, $registry, @@ -770,20 +784,14 @@ class Customer extends \Magento\Framework\Model\AbstractModel * Check if accounts confirmation is required in config * * @return bool + * @deprecated + * @see AccountConfirmation::isConfirmationRequired */ public function isConfirmationRequired() { - if ($this->canSkipConfirmation()) { - return false; - } - $websiteId = $this->getWebsiteId() ? $this->getWebsiteId() : null; - return (bool)$this->_scopeConfig->getValue( - self::XML_PATH_IS_CONFIRM, - ScopeInterface::SCOPE_WEBSITES, - $websiteId - ); + return $this->accountConfirmation->isConfirmationRequired($websiteId, $this->getId(), $this->getEmail()); } /** @@ -1156,6 +1164,8 @@ class Customer extends \Magento\Framework\Model\AbstractModel * Check whether confirmation may be skipped when registering using certain email address * * @return bool + * @deprecated + * @see AccountConfirmation::isConfirmationRequired */ protected function canSkipConfirmation() { diff --git a/app/code/Magento/Customer/Model/Options.php b/app/code/Magento/Customer/Model/Options.php index ee109dac08104ec9612843d50d5baab357dbb156..7747e309d82a60ca383c8fab7734ed6f4c649e4f 100644 --- a/app/code/Magento/Customer/Model/Options.php +++ b/app/code/Magento/Customer/Model/Options.php @@ -5,6 +5,7 @@ */ namespace Magento\Customer\Model; +use Magento\Config\Model\Config\Source\Nooptreq as NooptreqSource; use Magento\Customer\Helper\Address as AddressHelper; use Magento\Framework\Escaper; @@ -42,7 +43,10 @@ class Options */ public function getNamePrefixOptions($store = null) { - return $this->_prepareNamePrefixSuffixOptions($this->addressHelper->getConfig('prefix_options', $store)); + return $this->prepareNamePrefixSuffixOptions( + $this->addressHelper->getConfig('prefix_options', $store), + $this->addressHelper->getConfig('prefix_show', $store) == NooptreqSource::VALUE_OPTIONAL + ); } /** @@ -53,16 +57,34 @@ class Options */ public function getNameSuffixOptions($store = null) { - return $this->_prepareNamePrefixSuffixOptions($this->addressHelper->getConfig('suffix_options', $store)); + return $this->prepareNamePrefixSuffixOptions( + $this->addressHelper->getConfig('suffix_options', $store), + $this->addressHelper->getConfig('suffix_show', $store) == NooptreqSource::VALUE_OPTIONAL + ); + } + + /** + * @param $options + * @param bool $isOptional + * @return array|bool + * + * @deprecated + * @see prepareNamePrefixSuffixOptions() + */ + protected function _prepareNamePrefixSuffixOptions($options, $isOptional = false) + { + return $this->prepareNamePrefixSuffixOptions($options, $isOptional); } /** * Unserialize and clear name prefix or suffix options + * If field is optional, add an empty first option. * * @param string $options + * @param bool $isOptional * @return array|bool */ - protected function _prepareNamePrefixSuffixOptions($options) + private function prepareNamePrefixSuffixOptions($options, $isOptional = false) { $options = trim($options); if (empty($options)) { @@ -74,6 +96,10 @@ class Options $value = $this->escaper->escapeHtml(trim($value)); $result[$value] = $value; } + if ($isOptional && trim(current($options))) { + $result = array_merge([' ' => ' '], $result); + } + return $result; } } diff --git a/app/code/Magento/Customer/Setup/UpgradeData.php b/app/code/Magento/Customer/Setup/UpgradeData.php index b5aba18a92f2871b442393246cc7d0dab78dddeb..0ad36b1d6d11c35f55f2ebc15d05a8a36cd60ac1 100644 --- a/app/code/Magento/Customer/Setup/UpgradeData.php +++ b/app/code/Magento/Customer/Setup/UpgradeData.php @@ -159,6 +159,10 @@ class UpgradeData implements UpgradeDataInterface $this->upgradeVersionTwoZeroTwelve($customerSetup); } + if (version_compare($context->getVersion(), '2.0.13', '<')) { + $this->upgradeVersionTwoZeroThirteen($customerSetup); + } + $indexer = $this->indexerRegistry->get(Customer::CUSTOMER_GRID_INDEXER_ID); $indexer->reindexAll(); $this->eavConfig->clear(); @@ -663,4 +667,36 @@ class UpgradeData implements UpgradeDataInterface ['path = ?' => \Magento\Customer\Model\Customer::XML_PATH_CUSTOMER_RESET_PASSWORD_LINK_EXPIRATION_PERIOD] ); } + + /** + * @param CustomerSetup $customerSetup + */ + private function upgradeVersionTwoZeroThirteen(CustomerSetup $customerSetup) + { + $entityAttributes = [ + 'customer_address' => [ + 'firstname' => [ + 'input_filter' => 'trim' + ], + 'lastname' => [ + 'input_filter' => 'trim' + ], + 'middlename' => [ + 'input_filter' => 'trim' + ], + ], + 'customer' => [ + 'firstname' => [ + 'input_filter' => 'trim' + ], + 'lastname' => [ + 'input_filter' => 'trim' + ], + 'middlename' => [ + 'input_filter' => 'trim' + ], + ], + ]; + $this->upgradeAttributes($entityAttributes, $customerSetup); + } } diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmationTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..113f8c104a4ea62e26a9add25f847259611c5cbc --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Controller/Account/ConfirmationTest.php @@ -0,0 +1,116 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Test\Unit\Controller\Account; + +use Magento\Customer\Controller\Account\Confirmation; +use Magento\Framework\App\Request\Http; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class ConfirmationTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var Confirmation + */ + private $model; + + /** + * @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerSessionMock; + + /** + * @var \Magento\Framework\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var \Magento\Framework\View\Result\PageFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultPageFactoryMock; + + /** + * @var \Magento\Customer\Model\Url|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerUrlMock; + + /** + * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + public function setUp() + { + $this->customerSessionMock = $this->getMockBuilder(\Magento\Customer\Model\Session::class) + ->disableOriginalConstructor() + ->setMethods(['isLoggedIn']) + ->getMock(); + $this->contextMock = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class) + ->disableOriginalConstructor() + ->setMethods(['getRequest']) + ->getMock(); + $this->requestMock = $this->getMockBuilder(Http::class) + ->disableOriginalConstructor() + ->setMethods(['getPost', 'getParam']) + ->getMock(); + $this->contextMock->expects($this->any()) + ->method('getRequest') + ->willReturn($this->requestMock); + + $this->resultPageFactoryMock = $this->getMockBuilder(\Magento\Framework\View\Result\PageFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->customerUrlMock = $this->getMockBuilder(\Magento\Customer\Model\Url::class) + ->disableOriginalConstructor() + ->setMethods(['getLoginUrl']) + ->getMock(); + $this->model = (new ObjectManagerHelper($this))->getObject( + Confirmation::class, + [ + 'context' => $this->contextMock, + 'customerSession' => $this->customerSessionMock, + 'resultPageFactory' => $this->resultPageFactoryMock, + 'customerUrl' => $this->customerUrlMock, + ] + ); + } + + public function testGetLoginUrl() + { + $this->customerSessionMock->expects($this->once()) + ->method('isLoggedIn') + ->willReturn(false); + + $this->requestMock->expects($this->once())->method('getPost')->with('email')->willReturn(null); + + $resultPageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) + ->disableOriginalConstructor() + ->setMethods(['getLayout']) + ->getMock(); + + $this->resultPageFactoryMock->expects($this->once())->method('create')->willReturn($resultPageMock); + + $layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class) + ->disableOriginalConstructor() + ->setMethods(['getBlock']) + ->getMock(); + + $resultPageMock->expects($this->once())->method('getLayout')->willReturn($layoutMock); + + $blockMock = $this->getMockBuilder(\Magento\Framework\View\Element\Template::class) + ->disableOriginalConstructor() + ->setMethods(['setEmail', 'setLoginUrl']) + ->getMock(); + + $layoutMock->expects($this->once())->method('getBlock')->with('accountConfirmation')->willReturn($blockMock); + + $blockMock->expects($this->once())->method('setEmail')->willReturnSelf(); + $blockMock->expects($this->once())->method('setLoginUrl')->willReturnSelf(); + + $this->model->execute(); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountConfirmationTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountConfirmationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ae246665b28ed09e9100220f96a7d51838e935d6 --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountConfirmationTest.php @@ -0,0 +1,93 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Customer\Test\Unit\Model; + +use Magento\Customer\Model\AccountConfirmation; +use Magento\Store\Model\ScopeInterface; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Registry; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class AccountConfirmationTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var AccountConfirmation|\PHPUnit_Framework_MockObject_MockObject + */ + private $accountConfirmation; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfig; + + /** + * @var Registry|\PHPUnit_Framework_MockObject_MockObject + */ + private $registry; + + protected function setUp() + { + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); + $this->registry = $this->createMock(Registry::class); + + $this->accountConfirmation = new AccountConfirmation( + $this->scopeConfig, + $this->registry + ); + } + + /** + * @param $customerId + * @param $customerEmail + * @param $skipConfirmationIfEmail + * @param $isConfirmationEnabled + * @param $expected + * @dataProvider dataProviderIsConfirmationRequired + */ + public function testIsConfirmationRequired( + $customerId, + $customerEmail, + $skipConfirmationIfEmail, + $isConfirmationEnabled, + $expected + ) { + $websiteId = 1; + + $this->scopeConfig->expects($this->any()) + ->method('getValue') + ->with( + $this->accountConfirmation::XML_PATH_IS_CONFIRM, + ScopeInterface::SCOPE_WEBSITES, + $websiteId + )->willReturn($isConfirmationEnabled); + + $this->registry->expects($this->any()) + ->method('registry') + ->with('skip_confirmation_if_email') + ->willReturn($skipConfirmationIfEmail); + + self::assertEquals( + $expected, + $this->accountConfirmation->isConfirmationRequired($websiteId, $customerId, $customerEmail) + ); + } + + /** + * @return array + */ + public function dataProviderIsConfirmationRequired() + { + return [ + [null, 'customer@example.com', null, true, true], + [null, 'customer@example.com', null, false, false], + [1, 'customer@example.com', 'customer@example.com', true, false], + [1, 'customer@example.com', 'customer1@example.com', false, false], + [1, 'customer@example.com', 'customer1@example.com', true, true], + ]; + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php index 676e9c98a2008eebe06aca66f14602573f88c036..d72d7ec87ec3dba1454f76413f821f0159e01f7f 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Test\Unit\Model; use Magento\Customer\Model\AccountManagement; +use Magento\Customer\Model\AccountConfirmation; use Magento\Customer\Model\AuthenticationInterface; use Magento\Customer\Model\EmailNotificationInterface; use Magento\Framework\App\Area; @@ -120,6 +121,11 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase */ private $dateTimeFactory; + /** + * @var AccountConfirmation|\PHPUnit_Framework_MockObject_MockObject + */ + private $accountConfirmation; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -170,6 +176,7 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase ->getMock(); $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); + $this->accountConfirmation = $this->createMock(AccountConfirmation::class); $this->objectManagerHelper = new ObjectManagerHelper($this); $this->accountManagement = $this->objectManagerHelper->getObject( @@ -199,6 +206,7 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase 'objectFactory' => $this->objectFactory, 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, 'dateTimeFactory' => $this->dateTimeFactory, + 'accountConfirmation' => $this->accountConfirmation ] ); $reflection = new \ReflectionClass(get_class($this->accountManagement)); @@ -1467,14 +1475,12 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase } /** - * @param string|null $skipConfirmationIfEmail * @param int $isConfirmationRequired * @param string|null $confirmation * @param string $expected * @dataProvider dataProviderGetConfirmationStatus */ public function testGetConfirmationStatus( - $skipConfirmationIfEmail, $isConfirmationRequired, $confirmation, $expected @@ -1492,21 +1498,16 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase $customerMock->expects($this->any()) ->method('getConfirmation') ->willReturn($confirmation); - $customerMock->expects($this->any()) + $customerMock->expects($this->once()) ->method('getEmail') ->willReturn($customerEmail); - $customerMock->expects($this->any()) + $customerMock->expects($this->once()) ->method('getWebsiteId') ->willReturn($websiteId); - $this->registry->expects($this->once()) - ->method('registry') - ->with('skip_confirmation_if_email') - ->willReturn($skipConfirmationIfEmail); - - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->with(AccountManagement::XML_PATH_IS_CONFIRM, ScopeInterface::SCOPE_WEBSITES, $websiteId) + $this->accountConfirmation->expects($this->once()) + ->method('isConfirmationRequired') + ->with($websiteId, $customerId, $customerEmail) ->willReturn($isConfirmationRequired); $this->customerRepository->expects($this->once()) @@ -1523,11 +1524,11 @@ class AccountManagementTest extends \PHPUnit\Framework\TestCase public function dataProviderGetConfirmationStatus() { return [ - [null, 0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - ['test1@example.com', 0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - ['test2@example.com', 0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - ['test2@example.com', 1, null, AccountManagement::ACCOUNT_CONFIRMED], - ['test2@example.com', 1, 'test', AccountManagement::ACCOUNT_CONFIRMATION_REQUIRED], + [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], + [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], + [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], + [1, null, AccountManagement::ACCOUNT_CONFIRMED], + [1, 'test', AccountManagement::ACCOUNT_CONFIRMATION_REQUIRED], ]; } diff --git a/app/code/Magento/Customer/Test/Unit/Model/Checkout/ConfigProviderTest.php b/app/code/Magento/Customer/Test/Unit/Model/Checkout/ConfigProviderTest.php index 011ba9091eaf2f24b4ed26466524ee3780d81b7b..58b099a1d387d2229cab1dba2d9ec90377c743d5 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/Checkout/ConfigProviderTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/Checkout/ConfigProviderTest.php @@ -41,6 +41,11 @@ class ConfigProviderTest extends \PHPUnit\Framework\TestCase */ protected $store; + /** + * @var Url|\PHPUnit_Framework_MockObject_MockObject + */ + private $customerUrl; + protected function setUp() { $this->storeManager = $this->getMockForAbstractClass( @@ -49,12 +54,14 @@ class ConfigProviderTest extends \PHPUnit\Framework\TestCase '', false ); + $this->urlBuilder = $this->getMockForAbstractClass( \Magento\Framework\UrlInterface::class, [], '', false ); + $this->scopeConfig = $this->getMockForAbstractClass( \Magento\Framework\App\Config\ScopeConfigInterface::class, [], @@ -71,10 +78,13 @@ class ConfigProviderTest extends \PHPUnit\Framework\TestCase ['getBaseUrl'] ); + $this->customerUrl = $this->createMock(\Magento\Customer\Model\Url::class); + $this->provider = new ConfigProvider( $this->urlBuilder, $this->storeManager, - $this->scopeConfig + $this->scopeConfig, + $this->customerUrl ); } @@ -83,9 +93,8 @@ class ConfigProviderTest extends \PHPUnit\Framework\TestCase $loginUrl = 'http://url.test/customer/login'; $baseUrl = 'http://base-url.test'; - $this->urlBuilder->expects($this->exactly(2)) - ->method('getUrl') - ->with(Url::ROUTE_ACCOUNT_LOGIN) + $this->customerUrl->expects($this->exactly(2)) + ->method('getLoginUrl') ->willReturn($loginUrl); $this->storeManager->expects($this->once()) ->method('getStore') @@ -112,9 +121,8 @@ class ConfigProviderTest extends \PHPUnit\Framework\TestCase $loginUrl = 'http://base-url.test/customer/login'; $baseUrl = 'http://base-url.test'; - $this->urlBuilder->expects($this->exactly(2)) - ->method('getUrl') - ->with(Url::ROUTE_ACCOUNT_LOGIN) + $this->customerUrl->expects($this->exactly(2)) + ->method('getLoginUrl') ->willReturn($loginUrl); $this->storeManager->expects($this->once()) ->method('getStore') diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php index 8b3f7875e3c97d7badbca770d9edd25cb853ab2f..f5b7f08d2906d47849e4b2b96c529b12da21ab03 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php @@ -12,7 +12,7 @@ namespace Magento\Customer\Test\Unit\Model; use Magento\Customer\Model\Customer; -use Magento\Store\Model\ScopeInterface; +use Magento\Customer\Model\AccountConfirmation; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -63,6 +63,11 @@ class CustomerTest extends \PHPUnit\Framework\TestCase */ private $dataObjectProcessor; + /** + * @var AccountConfirmation|\PHPUnit_Framework_MockObject_MockObject + */ + private $accountConfirmation; + protected function setUp() { $this->_website = $this->createMock(\Magento\Store\Model\Website::class); @@ -94,6 +99,7 @@ class CustomerTest extends \PHPUnit\Framework\TestCase $this->registryMock = $this->createPartialMock(\Magento\Framework\Registry::class, ['registry']); $this->_encryptor = $this->createMock(\Magento\Framework\Encryption\EncryptorInterface::class); $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->accountConfirmation = $this->createMock(AccountConfirmation::class); $this->_model = $helper->getObject( \Magento\Customer\Model\Customer::class, [ @@ -105,7 +111,8 @@ class CustomerTest extends \PHPUnit\Framework\TestCase 'attributeFactory' => $this->attributeFactoryMock, 'registry' => $this->registryMock, 'resource' => $this->resourceMock, - 'dataObjectProcessor' => $this->dataObjectProcessor + 'dataObjectProcessor' => $this->dataObjectProcessor, + 'accountConfirmation' => $this->accountConfirmation ] ); } @@ -215,32 +222,27 @@ class CustomerTest extends \PHPUnit\Framework\TestCase /** * @param int $customerId * @param int $websiteId - * @param string|null $skipConfirmationIfEmail + * @param bool $isConfirmationRequired * @param bool $expected * @dataProvider dataProviderIsConfirmationRequired */ public function testIsConfirmationRequired( $customerId, $websiteId, - $skipConfirmationIfEmail, + $isConfirmationRequired, $expected ) { $customerEmail = 'test1@example.com'; - $this->registryMock->expects($this->any()) - ->method('registry') - ->with('skip_confirmation_if_email') - ->willReturn($skipConfirmationIfEmail); - - $this->_scopeConfigMock->expects($this->any()) - ->method('getValue') - ->with(Customer::XML_PATH_IS_CONFIRM, ScopeInterface::SCOPE_WEBSITES, $websiteId) - ->willReturn($expected); - $this->_model->setData('id', $customerId); $this->_model->setData('website_id', $websiteId); $this->_model->setData('email', $customerEmail); + $this->accountConfirmation->expects($this->once()) + ->method('isConfirmationRequired') + ->with($websiteId, $customerId, $customerEmail) + ->willReturn($isConfirmationRequired); + $this->assertEquals($expected, $this->_model->isConfirmationRequired()); } @@ -250,12 +252,9 @@ class CustomerTest extends \PHPUnit\Framework\TestCase public function dataProviderIsConfirmationRequired() { return [ - [null, null, null, false], - [1, 1, null, false], - [1, 1, 'test1@example.com', false], - [1, 1, 'test2@example.com', true], - [1, 0, 'test2@example.com', true], - [1, null, 'test2@example.com', true], + [null, null, false, false], + [1, 1, true, true], + [1, null, true, true], ]; } diff --git a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ConfirmationTest.php b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ConfirmationTest.php index e55cee49b5c94d5795c62076839fb823348110cb..b712c0f30b4307d8450013d4c0fb8bc96c6dae2a 100644 --- a/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ConfirmationTest.php +++ b/app/code/Magento/Customer/Test/Unit/Ui/Component/Listing/Column/ConfirmationTest.php @@ -5,13 +5,12 @@ */ namespace Magento\Customer\Test\Unit\Ui\Component\Listing\Column; -use Magento\Customer\Model\AccountManagement; +use Magento\Customer\Model\AccountConfirmation; use Magento\Customer\Ui\Component\Listing\Column\Confirmation; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponent\Processor; use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Store\Model\ScopeInterface; class ConfirmationTest extends \PHPUnit\Framework\TestCase { @@ -40,6 +39,11 @@ class ConfirmationTest extends \PHPUnit\Framework\TestCase */ protected $processor; + /** + * @var AccountConfirmation|\PHPUnit_Framework_MockObject_MockObject + */ + protected $accountConfirmation; + public function setup() { $this->processor = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponent\Processor::class) @@ -60,12 +64,15 @@ class ConfirmationTest extends \PHPUnit\Framework\TestCase $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) ->getMockForAbstractClass(); + $this->accountConfirmation = $this->createMock(AccountConfirmation::class); + $this->confirmation = new Confirmation( $this->context, $this->uiComponentFactory, $this->scopeConfig, [], - [] + [], + $this->accountConfirmation ); } @@ -81,12 +88,17 @@ class ConfirmationTest extends \PHPUnit\Framework\TestCase $expected ) { $websiteId = 1; + $customerId = 1; + $customerEmail = 'customer@example.com'; $dataSource = [ 'data' => [ 'items' => [ [ + 'id_field_name' => 'entity_id', + 'entity_id' => $customerId, 'confirmation' => $confirmation, + 'email' => $customerEmail, 'website_id' => [ $websiteId, ], @@ -100,9 +112,9 @@ class ConfirmationTest extends \PHPUnit\Framework\TestCase ->with($this->confirmation) ->willReturnSelf(); - $this->scopeConfig->expects($this->once()) - ->method('getValue') - ->with(AccountManagement::XML_PATH_IS_CONFIRM, ScopeInterface::SCOPE_WEBSITES, $websiteId) + $this->accountConfirmation->expects($this->once()) + ->method('isConfirmationRequired') + ->with($websiteId, $customerId, $customerEmail) ->willReturn($isConfirmationRequired); $this->confirmation->setData('name', 'confirmation'); diff --git a/app/code/Magento/Customer/Ui/Component/Listing/Column/Confirmation.php b/app/code/Magento/Customer/Ui/Component/Listing/Column/Confirmation.php index dcaaa665ad392e12f8a4bad3988a44e01a45c300..1786c52844a750ca8eba53335ddc1aa404faccd9 100644 --- a/app/code/Magento/Customer/Ui/Component/Listing/Column/Confirmation.php +++ b/app/code/Magento/Customer/Ui/Component/Listing/Column/Confirmation.php @@ -5,35 +5,42 @@ */ namespace Magento\Customer\Ui\Component\Listing\Column; -use Magento\Customer\Model\AccountManagement; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Store\Model\ScopeInterface; use Magento\Ui\Component\Listing\Columns\Column; +use Magento\Framework\App\ObjectManager; +use Magento\Customer\Model\AccountConfirmation; +/** + * Class Confirmation column. + */ class Confirmation extends Column { /** - * @var ScopeConfigInterface + * @var AccountConfirmation */ - private $scopeConfig; + private $accountConfirmation; /** * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory - * @param ScopeConfigInterface $scopeConfig + * @param ScopeConfigInterface $scopeConfig @deprecated * @param array $components * @param array $data + * @param AccountConfirmation $accountConfirmation + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( ContextInterface $context, UiComponentFactory $uiComponentFactory, ScopeConfigInterface $scopeConfig, array $components, - array $data + array $data, + AccountConfirmation $accountConfirmation = null ) { - $this->scopeConfig = $scopeConfig; + $this->accountConfirmation = $accountConfirmation ?: ObjectManager::getInstance() + ->get(AccountConfirmation::class); parent::__construct($context, $uiComponentFactory, $components, $data); } @@ -58,7 +65,13 @@ class Confirmation extends Column */ private function getFieldLabel(array $item) { - if ($this->isConfirmationRequired($item)) { + $isConfirmationRequired = $this->accountConfirmation->isConfirmationRequired( + $item['website_id'][0], + $item[$item['id_field_name']], + $item['email'] + ); + + if ($isConfirmationRequired) { if ($item[$this->getData('name')] === null) { return __('Confirmed'); } @@ -66,19 +79,4 @@ class Confirmation extends Column } return __('Confirmation Not Required'); } - - /** - * Check if confirmation is required - * - * @param array $item - * @return bool - */ - private function isConfirmationRequired(array $item) - { - return (bool)$this->scopeConfig->getValue( - AccountManagement::XML_PATH_IS_CONFIRM, - ScopeInterface::SCOPE_WEBSITES, - $item['website_id'][0] - ); - } } diff --git a/app/code/Magento/Customer/etc/adminhtml/system.xml b/app/code/Magento/Customer/etc/adminhtml/system.xml index fa1566af0c9436cca2061221a7f0811434ba4034..31e968de14d99f49a6a7201141ca3567863366d2 100644 --- a/app/code/Magento/Customer/etc/adminhtml/system.xml +++ b/app/code/Magento/Customer/etc/adminhtml/system.xml @@ -210,7 +210,7 @@ <field id="prefix_options" translate="label comment" sortOrder="30" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Prefix Dropdown Options</label> <comment> - <![CDATA[Semicolon (;) separated values.<br/>Put semicolon in the beginning for empty first option.<br/>Leave empty for open text field.]]> + <![CDATA[Semicolon (;) separated values.<br/>Leave empty for open text field.]]> </comment> </field> <field id="middlename_show" translate="label comment" type="select" sortOrder="40" showInDefault="1" showInWebsite="1" showInStore="0"> @@ -228,7 +228,7 @@ <field id="suffix_options" translate="label comment" sortOrder="60" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Suffix Dropdown Options</label> <comment> - <![CDATA[Semicolon (;) separated values.<br/>Put semicolon in the beginning for empty first option.<br/>Leave empty for open text field.]]> + <![CDATA[Semicolon (;) separated values.<br/>Leave empty for open text field.]]> </comment> </field> <field id="dob_show" translate="label" type="select" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="0"> diff --git a/app/code/Magento/Customer/etc/module.xml b/app/code/Magento/Customer/etc/module.xml index 3f0d42b12649ae4e096808e126566498289ce19a..2dfe561d0da8fb8afb17db8e8ddd24f96938acbf 100644 --- a/app/code/Magento/Customer/etc/module.xml +++ b/app/code/Magento/Customer/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Customer" setup_version="2.0.12"> + <module name="Magento_Customer" setup_version="2.0.13"> <sequence> <module name="Magento_Eav"/> <module name="Magento_Directory"/> diff --git a/app/code/Magento/Customer/i18n/en_US.csv b/app/code/Magento/Customer/i18n/en_US.csv index aececbf6deeb4699c8db9cddcb54bf353324f553..8768426b5c9497c88e1ad53437e03a4ec10945c4 100644 --- a/app/code/Magento/Customer/i18n/en_US.csv +++ b/app/code/Magento/Customer/i18n/en_US.csv @@ -479,9 +479,9 @@ Strong,Strong "The title that goes before name (Mr., Mrs., etc.)","The title that goes before name (Mr., Mrs., etc.)" "Prefix Dropdown Options","Prefix Dropdown Options" " - Semicolon (;) separated values.<br/>Put semicolon in the beginning for empty first option.<br/>Leave empty for open text field. + Semicolon (;) separated values.<br/>Leave empty for open text field. "," - Semicolon (;) separated values.<br/>Put semicolon in the beginning for empty first option.<br/>Leave empty for open text field. + Semicolon (;) separated values.<br/>Leave empty for open text field. " "Show Middle Name (initial)","Show Middle Name (initial)" "Always optional.","Always optional." diff --git a/app/code/Magento/Customer/view/frontend/web/js/invalidation-processor.js b/app/code/Magento/Customer/view/frontend/web/js/invalidation-processor.js index d99574ec3dfbf7f836d7b604ad728d4bc82cb9ea..a6ae8ff043aa8944bc3d17a201880b2ee5543ea4 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/invalidation-processor.js +++ b/app/code/Magento/Customer/view/frontend/web/js/invalidation-processor.js @@ -1,5 +1,5 @@ /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define([ diff --git a/app/code/Magento/Customer/view/frontend/web/js/invalidation-rules/website-rule.js b/app/code/Magento/Customer/view/frontend/web/js/invalidation-rules/website-rule.js index eb7f101a6d47e0fff2c0aff12a0cb76ef4eaf10f..846edb2c836fad83cbee73230cbaf9a50800efc8 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/invalidation-rules/website-rule.js +++ b/app/code/Magento/Customer/view/frontend/web/js/invalidation-rules/website-rule.js @@ -1,5 +1,5 @@ /** - * Copyright © 2013-2017 Magento, Inc. All rights reserved. + * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ define([ diff --git a/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html b/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html index ad3d62f6c1c2796038cc7c969316eff4c7996edd..6b3a232cd3e3912473bc5e7619c2c2cecf006a09 100644 --- a/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html +++ b/app/code/Magento/Customer/view/frontend/web/template/authentication-popup.html @@ -54,10 +54,10 @@ id="login-form"> <div class="fieldset login" data-bind="attr: {'data-hasrequired': $t('* Required Fields')}"> <div class="field email required"> - <label class="label" for="email"><span data-bind="i18n: 'Email Address'"></span></label> + <label class="label" for="customer-email"><span data-bind="i18n: 'Email Address'"></span></label> <div class="control"> <input name="username" - id="email" + id="customer-email" type="email" class="input-text" data-bind="attr: {autocomplete: autocomplete}" diff --git a/app/code/Magento/Deploy/Console/Command/SetModeCommand.php b/app/code/Magento/Deploy/Console/Command/SetModeCommand.php index c4485656fb88465cee36229105fcf87ffd349d79..55b4a4f4f9b6a70edd682046cb051b7db3732061 100644 --- a/app/code/Magento/Deploy/Console/Command/SetModeCommand.php +++ b/app/code/Magento/Deploy/Console/Command/SetModeCommand.php @@ -101,6 +101,9 @@ class SetModeCommand extends Command $modeController->enableProductionMode(); } break; + case State::MODE_DEFAULT: + $modeController->enableDefaultMode(); + break; default: throw new LocalizedException(__('Cannot switch into given mode "%1"', $toMode)); } diff --git a/app/code/Magento/Deploy/Model/Mode.php b/app/code/Magento/Deploy/Model/Mode.php index 3810ef0953124521932c95e78e0e482fff832b00..792ee7f1b79685cc6717ac749850a07e1070447a 100644 --- a/app/code/Magento/Deploy/Model/Mode.php +++ b/app/code/Magento/Deploy/Model/Mode.php @@ -177,6 +177,25 @@ class Mode $this->setStoreMode(State::MODE_DEVELOPER); } + /** + * Enable Default mode + * + * @return void + */ + public function enableDefaultMode() + { + $this->filesystem->cleanupFilesystem( + [ + DirectoryList::CACHE, + DirectoryList::GENERATED_CODE, + DirectoryList::GENERATED_METADATA, + DirectoryList::TMP_MATERIALIZATION_DIR, + DirectoryList::STATIC_VIEW, + ] + ); + $this->setStoreMode(State::MODE_DEFAULT); + } + /** * Get current mode information * diff --git a/app/code/Magento/Deploy/Test/Unit/Console/Command/SetModeCommandTest.php b/app/code/Magento/Deploy/Test/Unit/Console/Command/SetModeCommandTest.php index caa310535f3569bbe31836d3067906e93ec905e6..a49bae655e87f42899976cf2118d46a1c426a43b 100644 --- a/app/code/Magento/Deploy/Test/Unit/Console/Command/SetModeCommandTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Console/Command/SetModeCommandTest.php @@ -67,6 +67,18 @@ class SetModeCommandTest extends \PHPUnit\Framework\TestCase ); } + public function testSetDefaultMode() + { + $this->modeMock->expects($this->once())->method('enableDefaultMode'); + + $tester = new CommandTester($this->command); + $tester->execute(['mode' => 'default']); + $this->assertContains( + "default mode", + $tester->getDisplay() + ); + } + public function testSetProductionSkipCompilation() { $this->modeMock->expects($this->once())->method('enableProductionModeMinimal'); diff --git a/app/code/Magento/Developer/Model/XmlCatalog/Format/PhpStorm.php b/app/code/Magento/Developer/Model/XmlCatalog/Format/PhpStorm.php index 30684b7177c997f0d2b1ddf9bcf55ea9f28596f5..1ced906cce7637251778a877ceca14fbe1f69418 100644 --- a/app/code/Magento/Developer/Model/XmlCatalog/Format/PhpStorm.php +++ b/app/code/Magento/Developer/Model/XmlCatalog/Format/PhpStorm.php @@ -6,11 +6,12 @@ namespace Magento\Developer\Model\XmlCatalog\Format; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\DomDocument\DomDocumentFactory; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Filesystem\Directory\ReadFactory; use Magento\Framework\Filesystem\Directory\ReadInterface; -use Magento\Framework\Filesystem\Directory\WriteFactory; -use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Filesystem\File\WriteFactory; /** * Class PhpStorm generates URN catalog for PhpStorm 9 @@ -23,20 +24,28 @@ class PhpStorm implements FormatInterface private $currentDirRead; /** - * @var \Magento\Framework\Filesystem\File\WriteFactory + * @var WriteFactory */ private $fileWriteFactory; + /** + * @var DomDocumentFactory + */ + private $domDocumentFactory; + /** * @param ReadFactory $readFactory - * @param \Magento\Framework\Filesystem\File\WriteFactory $fileWriteFactory + * @param WriteFactory $fileWriteFactory + * @param DomDocumentFactory $domDocumentFactory */ public function __construct( ReadFactory $readFactory, - \Magento\Framework\Filesystem\File\WriteFactory $fileWriteFactory + WriteFactory $fileWriteFactory, + DomDocumentFactory $domDocumentFactory = null ) { $this->currentDirRead = $readFactory->create(getcwd()); $this->fileWriteFactory = $fileWriteFactory; + $this->domDocumentFactory = $domDocumentFactory ?: ObjectManager::getInstance()->get(DomDocumentFactory::class); } /** @@ -57,26 +66,21 @@ class PhpStorm implements FormatInterface \Magento\Framework\Filesystem\DriverPool::FILE, 'r' ); - $dom = new \DOMDocument(); - $dom->loadXML($file->readAll()); + $dom = $this->domDocumentFactory->create(); + $fileContent = $file->readAll(); + if (!empty($fileContent)) { + $dom->loadXML($fileContent); + } else { + $this->initEmptyFile($dom); + } $xpath = new \DOMXPath($dom); $nodeList = $xpath->query('/project'); $projectNode = $nodeList->item(0); $file->close(); } catch (FileSystemException $f) { //create file if does not exists - $dom = new \DOMDocument(); - $projectNode = $dom->createElement('project'); - - //PhpStorm 9 version for component is "4" - $projectNode->setAttribute('version', '4'); - $dom->appendChild($projectNode); - $rootComponentNode = $dom->createElement('component'); - - //PhpStorm 9 version for ProjectRootManager is "2" - $rootComponentNode->setAttribute('version', '2'); - $rootComponentNode->setAttribute('name', 'ProjectRootManager'); - $projectNode->appendChild($rootComponentNode); + $dom = $this->domDocumentFactory->create(); + $projectNode = $this->initEmptyFile($dom); } $xpath = new \DOMXPath($dom); @@ -103,4 +107,26 @@ class PhpStorm implements FormatInterface $file->write($dom->saveXML()); $file->close(); } + + /** + * Setup basic empty dom elements + * + * @param \DOMDocument $dom + * @return \DOMElement + */ + private function initEmptyFile(\DOMDocument $dom) + { + $projectNode = $dom->createElement('project'); + + //PhpStorm 9 version for component is "4" + $projectNode->setAttribute('version', '4'); + $dom->appendChild($projectNode); + $rootComponentNode = $dom->createElement('component'); + + //PhpStorm 9 version for ProjectRootManager is "2" + $rootComponentNode->setAttribute('version', '2'); + $rootComponentNode->setAttribute('name', 'ProjectRootManager'); + $projectNode->appendChild($rootComponentNode); + return $projectNode; + } } diff --git a/app/code/Magento/Directory/Model/PriceCurrency.php b/app/code/Magento/Directory/Model/PriceCurrency.php index a211242d377f3de055fe9a4023b97e9fbf1ae6d7..07d2e60d61335da7848956e165c0da3676876c49 100644 --- a/app/code/Magento/Directory/Model/PriceCurrency.php +++ b/app/code/Magento/Directory/Model/PriceCurrency.php @@ -77,8 +77,7 @@ class PriceCurrency implements \Magento\Framework\Pricing\PriceCurrencyInterface $scope = null, $currency = null ) { - return $this->getCurrency($scope, $currency) - ->formatPrecision($amount, $precision, [], $includeContainer); + return $this->createCurrency($scope, $currency)->formatPrecision($amount, $precision, [], $includeContainer); } /** @@ -101,20 +100,7 @@ class PriceCurrency implements \Magento\Framework\Pricing\PriceCurrencyInterface */ public function getCurrency($scope = null, $currency = null) { - if ($currency instanceof Currency) { - $currentCurrency = $currency; - } elseif (is_string($currency)) { - $currency = $this->currencyFactory->create() - ->load($currency); - $baseCurrency = $this->getStore($scope) - ->getBaseCurrency(); - $currentCurrency = $baseCurrency->getRate($currency) ? $currency : $baseCurrency; - } else { - $currentCurrency = $this->getStore($scope) - ->getCurrentCurrency(); - } - - return $currentCurrency; + return $this->createCurrency($scope, $currency, true); } /** @@ -157,4 +143,30 @@ class PriceCurrency implements \Magento\Framework\Pricing\PriceCurrencyInterface { return round($price, 2); } + + /** + * Get currency considering currency rate configuration. + * + * @param null|string|bool|int|\Magento\Framework\App\ScopeInterface $scope + * @param \Magento\Framework\Model\AbstractModel|string|null $currency + * @param bool $includeRate + * + * @return Currency + */ + private function createCurrency($scope, $currency, bool $includeRate = false) + { + if ($currency instanceof Currency) { + $currentCurrency = $currency; + } elseif (is_string($currency)) { + $currentCurrency = $this->currencyFactory->create()->load($currency); + if ($includeRate) { + $baseCurrency = $this->getStore($scope)->getBaseCurrency(); + $currentCurrency = $baseCurrency->getRate($currentCurrency) ? $currentCurrency : $baseCurrency; + } + } else { + $currentCurrency = $this->getStore($scope)->getCurrentCurrency(); + } + + return $currentCurrency; + } } diff --git a/app/code/Magento/Directory/etc/adminhtml/system.xml b/app/code/Magento/Directory/etc/adminhtml/system.xml index 21eccd5bd521e3257696e51ea3755361cab86ec4..15a82e006bffee8bc7c50abf22b00088bff74d89 100644 --- a/app/code/Magento/Directory/etc/adminhtml/system.xml +++ b/app/code/Magento/Directory/etc/adminhtml/system.xml @@ -109,6 +109,7 @@ <field id="state_required" translate="label" type="multiselect" sortOrder="1" showInDefault="1" showInWebsite="0" showInStore="0"> <label>State is Required for</label> <source_model>Magento\Directory\Model\Config\Source\Country</source_model> + <can_be_empty>1</can_be_empty> </field> <field id="display_all" translate="label" type="select" sortOrder="8" showInDefault="1" showInWebsite="0" showInStore="0"> <label>Allow to Choose State if It is Optional for Country</label> diff --git a/app/code/Magento/Eav/Model/Attribute.php b/app/code/Magento/Eav/Model/Attribute.php index 64504b59fe9c96ac318cd23a5b83413477cc1998..e53f3ccc82a755eb71272bfd600a987da58b54e7 100644 --- a/app/code/Magento/Eav/Model/Attribute.php +++ b/app/code/Magento/Eav/Model/Attribute.php @@ -62,7 +62,7 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute */ public function getWebsite() { - if (is_null($this->_website)) { + if ($this->_website === null) { $this->_website = $this->_storeManager->getWebsite(); } @@ -88,7 +88,7 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute public function getUsedInForms() { $forms = $this->getData('used_in_forms'); - if (is_null($forms)) { + if ($forms === null) { $forms = $this->_getResource()->getUsedInForms($this); $this->setData('used_in_forms', $forms); } diff --git a/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php b/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php index ed052cb71e2fa094f41225f18ace2f84ec4285fd..12023acc3b33b37bacbb2f63e7e5e3f32adccd96 100644 --- a/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php +++ b/app/code/Magento/Eav/Model/Attribute/Data/AbstractData.php @@ -205,7 +205,7 @@ abstract class AbstractData */ public function getExtractedData($index = null) { - if (!is_null($index)) { + if ($index !== null) { if (isset($this->_extractedData[$index])) { return $this->_extractedData[$index]; } @@ -262,9 +262,9 @@ abstract class AbstractData */ protected function _dateFilterFormat($format = null) { - if (is_null($format)) { + if ($format === null) { // get format - if (is_null($this->_dateFilterFormat)) { + if ($this->_dateFilterFormat === null) { $this->_dateFilterFormat = \IntlDateFormatter::SHORT; } return $this->_localeDate->getDateFormat($this->_dateFilterFormat); diff --git a/app/code/Magento/Eav/Model/Attribute/GroupRepository.php b/app/code/Magento/Eav/Model/Attribute/GroupRepository.php index 9d0fa78668382d5c8b99641cbe844a66ad83c825..0714f8efac88cf6f6d51455267c99889f955793b 100644 --- a/app/code/Magento/Eav/Model/Attribute/GroupRepository.php +++ b/app/code/Magento/Eav/Model/Attribute/GroupRepository.php @@ -117,16 +117,6 @@ class GroupRepository implements \Magento\Eav\Api\AttributeGroupRepositoryInterf */ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria) { - $attributeSetId = $this->retrieveAttributeSetIdFromSearchCriteria($searchCriteria); - if (!$attributeSetId) { - throw InputException::requiredField('attribute_set_id'); - } - try { - $this->setRepository->get($attributeSetId); - } catch (\Exception $exception) { - throw NoSuchEntityException::singleField('attributeSetId', $attributeSetId); - } - /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\Collection $collection */ $collection = $this->groupListFactory->create(); $this->joinProcessor->process($collection); @@ -188,6 +178,7 @@ class GroupRepository implements \Magento\Eav\Api\AttributeGroupRepositoryInterf /** * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria * @return null|string + * @deprecated */ protected function retrieveAttributeSetIdFromSearchCriteria( \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria diff --git a/app/code/Magento/Eav/Model/Config.php b/app/code/Magento/Eav/Model/Config.php index cc68709cd3b038151e85c2495be00ee6a0e6fe27..0eecca21b0d542982e3dcfe09a88529f3c1c9e22 100644 --- a/app/code/Magento/Eav/Model/Config.php +++ b/app/code/Magento/Eav/Model/Config.php @@ -503,6 +503,7 @@ class Config } if (isset($this->attributes[$entityTypeCode][$code])) { + \Magento\Framework\Profiler::stop('EAV: ' . __METHOD__); return $this->attributes[$entityTypeCode][$code]; } diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php index 0c74b84457d408b5a9920334e3724563c73ba187..feb2c4d7d3f9faed766a36414d1f9a1d1ffca2cf 100644 --- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php +++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php @@ -13,6 +13,7 @@ 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\Framework\App\Config\Element; +use Magento\Framework\DataObject; use Magento\Framework\DB\Adapter\DuplicateException; use Magento\Framework\Exception\AlreadyExistsException; use Magento\Framework\Exception\LocalizedException; @@ -62,6 +63,13 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac */ protected $_attributesByCode = []; + /** + * Attributes stored by scope (store id and attribute set id). + * + * @var array + */ + private $attributesByScope = []; + /** * Two-dimensional array by table name and attribute name * @@ -473,6 +481,46 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac return $this; } + /** + * Adding attribute to entity by scope. + * + * @param AbstractAttribute $attribute + * @param DataObject|null $entity + * @return $this + */ + public function addAttributeByScope(AbstractAttribute $attribute, $entity = null) + { + $suffix = $entity !== null ? $this->getAttributesCacheSuffix($entity) : '0-0'; + $attributeCode = $attribute->getAttributeCode(); + $this->attributesByScope[$suffix][$attributeCode] = $attribute; + return $this->addAttribute($attribute); + } + + /** + * Get attributes by scope + * + * @return array + */ + private function getAttributesByScope($suffix) + { + return !empty($this->attributesByScope[$suffix]) + ? $this->attributesByScope[$suffix] + : $this->getAttributesByCode(); + } + + /** + * Get attributes cache suffix. + * + * @param DataObject $object + * @return string + */ + private function getAttributesCacheSuffix(DataObject $object) + { + $attributeSetId = $object->getAttributeSetId() ?: 0; + $storeId = $object->getStoreId() ?: 0; + return $storeId . '-' . $attributeSetId; + } + /** * Retrieve partial load flag * @@ -506,7 +554,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Retrieve configuration for all attributes * - * @param null|\Magento\Framework\DataObject $object + * @param null|DataObject $object * @return $this */ public function loadAllAttributes($object = null) @@ -566,7 +614,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Check whether the attribute is Applicable to the object * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @param AbstractAttribute $attribute * @return bool * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -611,7 +659,8 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac break; } $results = []; - foreach ($this->getAttributesByCode() as $attrCode => $attribute) { + $suffix = $this->getAttributesCacheSuffix($args[0]); + foreach ($this->getAttributesByScope($suffix) as $attrCode => $attribute) { if (isset($args[0]) && is_object($args[0]) && !$this->_isApplicableAttribute($args[0], $attribute)) { continue; } @@ -830,7 +879,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Validate all object's attributes against configuration * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @throws \Magento\Eav\Model\Entity\Attribute\Exception * @return true|array */ @@ -856,10 +905,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Set new increment id to object * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @return $this */ - public function setNewIncrementId(\Magento\Framework\DataObject $object) + public function setNewIncrementId(DataObject $object) { if ($object->getIncrementId()) { return $this; @@ -878,7 +927,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac * Check attribute unique value * * @param AbstractAttribute $attribute - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @return bool */ public function checkAttributeUniqueValue(AbstractAttribute $attribute, $object) @@ -1051,7 +1100,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Retrieve select object for loading base entity row * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @param string|int $rowId * @return \Magento\Framework\DB\Select * @SuppressWarnings(PHPMD.UnusedFormalParameter) @@ -1071,7 +1120,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Retrieve select object for loading entity attributes values * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @param string $table * @return \Magento\Framework\DB\Select */ @@ -1091,7 +1140,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Initialize attribute value for object * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @param array $valueRow * @return $this */ @@ -1181,8 +1230,8 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Retrieve Object instance with original data * - * @param \Magento\Framework\DataObject $object - * @return \Magento\Framework\DataObject + * @param DataObject $object + * @return DataObject */ protected function _getOrigObject($object) { @@ -1245,7 +1294,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac $origData = $this->_getOrigObject($newObject)->getOrigData(); } - if (is_null($origData)) { + if ($origData === null) { $origData = []; } @@ -1422,7 +1471,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Process base row */ - $entityObject = new \Magento\Framework\DataObject($entityRow); + $entityObject = new DataObject($entityRow); $entityRow = $this->_prepareDataForTable($entityObject, $entityTable); if ($insertEntity) { if (!empty($entityId)) { @@ -1477,7 +1526,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Insert entity attribute value * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @param AbstractAttribute $attribute * @param mixed $value * @return $this @@ -1490,7 +1539,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Update entity attribute value * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @param AbstractAttribute $attribute * @param mixed $valueId * @param mixed $value @@ -1585,10 +1634,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Delete entity attribute values * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @param string $table * @param array $info - * @return \Magento\Framework\DataObject + * @return DataObject * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function _deleteAttributes($object, $table, $info) @@ -1614,13 +1663,13 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Save attribute * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @param string $attributeCode * @return $this * @throws \Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function saveAttribute(\Magento\Framework\DataObject $object, $attributeCode) + public function saveAttribute(DataObject $object, $attributeCode) { $attribute = $this->getAttribute($attributeCode); $backend = $attribute->getBackend(); @@ -1666,8 +1715,8 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Return attribute row to prepare where statement * - * @param \Magento\Framework\DataObject $entity - * @param \Magento\Framework\DataObject $object + * @param DataObject $entity + * @param DataObject $object * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute * @return array */ @@ -1688,7 +1737,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Delete entity using current object's data * - * @param \Magento\Framework\DataObject|int|string $object + * @param DataObject|int|string $object * @return $this * @throws \Exception * @SuppressWarnings(PHPMD.UnusedLocalVariable) @@ -1730,7 +1779,7 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Evaluate Delete operations * - * @param \Magento\Framework\DataObject|int|string $object + * @param DataObject|int|string $object * @param string|int $id * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection * @return void @@ -1764,10 +1813,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * After Load Entity process * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @return $this */ - protected function _afterLoad(\Magento\Framework\DataObject $object) + protected function _afterLoad(DataObject $object) { \Magento\Framework\Profiler::start('after_load'); $this->walkAttributes('backend/afterLoad', [$object]); @@ -1778,10 +1827,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Before delete Entity process * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @return $this */ - protected function _beforeSave(\Magento\Framework\DataObject $object) + protected function _beforeSave(DataObject $object) { $this->walkAttributes('backend/beforeSave', [$object]); return $this; @@ -1790,10 +1839,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * After Save Entity process * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @return $this */ - protected function _afterSave(\Magento\Framework\DataObject $object) + protected function _afterSave(DataObject $object) { $this->walkAttributes('backend/afterSave', [$object]); return $this; @@ -1802,10 +1851,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Before Delete Entity process * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @return $this */ - protected function _beforeDelete(\Magento\Framework\DataObject $object) + protected function _beforeDelete(DataObject $object) { $this->walkAttributes('backend/beforeDelete', [$object]); return $this; @@ -1814,10 +1863,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * After delete entity process * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @return $this */ - protected function _afterDelete(\Magento\Framework\DataObject $object) + protected function _afterDelete(DataObject $object) { $this->walkAttributes('backend/afterDelete', [$object]); return $this; @@ -1887,10 +1936,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Perform actions after entity load * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @since 100.1.0 */ - public function afterLoad(\Magento\Framework\DataObject $object) + public function afterLoad(DataObject $object) { $this->_afterLoad($object); } @@ -1898,10 +1947,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Perform actions before entity save * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @since 100.1.0 */ - public function beforeSave(\Magento\Framework\DataObject $object) + public function beforeSave(DataObject $object) { $this->_beforeSave($object); } @@ -1909,10 +1958,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Perform actions after entity save * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @since 100.1.0 */ - public function afterSave(\Magento\Framework\DataObject $object) + public function afterSave(DataObject $object) { $this->_afterSave($object); } @@ -1920,10 +1969,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Perform actions before entity delete * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @since 100.1.0 */ - public function beforeDelete(\Magento\Framework\DataObject $object) + public function beforeDelete(DataObject $object) { $this->_beforeDelete($object); } @@ -1931,10 +1980,10 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac /** * Perform actions after entity delete * - * @param \Magento\Framework\DataObject $object + * @param DataObject $object * @since 100.1.0 */ - public function afterDelete(\Magento\Framework\DataObject $object) + public function afterDelete(DataObject $object) { $this->_afterDelete($object); } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/Datetime.php b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/Datetime.php index 786060ea7b312cec5da89d4325bb309375816044..1a5f0a7811de34d440e5a18ca7a2943f0f56cc06 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/Datetime.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/Datetime.php @@ -49,7 +49,7 @@ class Datetime extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBacke throw new \Magento\Framework\Exception\LocalizedException(__('Invalid date')); } - if (is_null($value)) { + if ($value === null) { $value = $object->getData($attributeName); } diff --git a/app/code/Magento/Eav/Model/Entity/AttributeLoader.php b/app/code/Magento/Eav/Model/Entity/AttributeLoader.php index a925f8e78ff336e0bea3811e7347ce1b5b81722d..bb452ad54ac842a42710c40af6694a4e7c430499 100644 --- a/app/code/Magento/Eav/Model/Entity/AttributeLoader.php +++ b/app/code/Magento/Eav/Model/Entity/AttributeLoader.php @@ -51,13 +51,13 @@ class AttributeLoader implements AttributeLoaderInterface * Retrieve configuration for all attributes * * @param AbstractEntity $resource - * @param DataObject|null $object + * @param DataObject|null $entity * @return AbstractEntity * @throws LocalizedException */ - public function loadAllAttributes(AbstractEntity $resource, DataObject $object = null) + public function loadAllAttributes(AbstractEntity $resource, DataObject $entity = null) { - $attributes = $this->config->getEntityAttributes($resource->getEntityType(), $object); + $attributes = $this->config->getEntityAttributes($resource->getEntityType(), $entity); $attributeCodes = array_keys($attributes); /** * Check and init default attributes @@ -67,10 +67,10 @@ class AttributeLoader implements AttributeLoaderInterface $resource->unsetAttributes(); foreach ($defaultAttributesCodes as $attributeCode) { - $resource->addAttribute($this->_getDefaultAttribute($resource, $attributeCode)); + $resource->addAttributeByScope($this->_getDefaultAttribute($resource, $attributeCode), $entity); } foreach ($attributes as $attributeCode => $attribute) { - $resource->addAttribute($attribute); + $resource->addAttributeByScope($attribute, $entity); } return $resource; } diff --git a/app/code/Magento/Eav/Model/Entity/AttributeLoaderInterface.php b/app/code/Magento/Eav/Model/Entity/AttributeLoaderInterface.php index f3f8745fc535bd6f35e6993dbfe729f7ed1d0076..2440492070c66493d8a8cb78ed2a7872d171582a 100644 --- a/app/code/Magento/Eav/Model/Entity/AttributeLoaderInterface.php +++ b/app/code/Magento/Eav/Model/Entity/AttributeLoaderInterface.php @@ -17,8 +17,8 @@ interface AttributeLoaderInterface * Retrieve configuration for all attributes * * @param AbstractEntity $resource - * @param DataObject|null $object + * @param DataObject|null $entity * @return AbstractEntity */ - public function loadAllAttributes(AbstractEntity $resource, DataObject $object = null); + public function loadAllAttributes(AbstractEntity $resource, DataObject $entity = null); } diff --git a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php index bd3fd17393d7cfb80a8aff1fd3f7a782063f59dd..f623cfc8c7b37e475fd55b25164cbbd06811e7a3 100644 --- a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php +++ b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php @@ -920,6 +920,7 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn foreach ($this->_items as $item) { $item->setOrigData(); $this->beforeAddLoadedItem($item); + $item->setDataChanges(false); } \Magento\Framework\Profiler::stop('set_orig_data'); diff --git a/app/code/Magento/Eav/Test/Unit/Model/Attribute/GroupRepositoryTest.php b/app/code/Magento/Eav/Test/Unit/Model/Attribute/GroupRepositoryTest.php index c07122e049a7483515572b18b18f21d8573203b9..9b0f9704887bbd677830615d138e099fa4538b2d 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Attribute/GroupRepositoryTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Attribute/GroupRepositoryTest.php @@ -266,8 +266,6 @@ class GroupRepositoryTest extends \PHPUnit\Framework\TestCase */ public function testGetList() { - $attributeSetId = 'filter'; - $filterInterfaceMock = $this->getMockBuilder(\Magento\Framework\Api\Search\FilterGroup::class) ->disableOriginalConstructor() ->setMethods([ @@ -275,24 +273,18 @@ class GroupRepositoryTest extends \PHPUnit\Framework\TestCase 'getValue', ]) ->getMock(); - $filterInterfaceMock->expects($this->once()) - ->method('getField') - ->willReturn('attribute_set_id'); - $filterInterfaceMock->expects($this->once()) - ->method('getValue') - ->willReturn($attributeSetId); $filterGroupMock = $this->getMockBuilder(\Magento\Framework\Api\Search\FilterGroup::class) ->disableOriginalConstructor() ->getMock(); - $filterGroupMock->expects($this->once()) + $filterGroupMock->expects($this->any()) ->method('getFilters') ->willReturn([$filterInterfaceMock]); $searchCriteriaMock = $this->getMockBuilder(\Magento\Framework\Api\SearchCriteriaInterface::class) ->disableOriginalConstructor() ->getMock(); - $searchCriteriaMock->expects($this->once()) + $searchCriteriaMock->expects($this->any()) ->method('getFilterGroups') ->willReturn([$filterGroupMock]); @@ -324,52 +316,6 @@ class GroupRepositoryTest extends \PHPUnit\Framework\TestCase $this->assertEquals($searchResultsMock, $this->model->getList($searchCriteriaMock)); } - /** - * Test get list with invalid input exception - * - * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage attribute_set_id is a required field. - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @return void - */ - public function testGetListWithInvalidInputException() - { - $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaInterface::class); - $searchCriteriaMock->expects($this->once())->method('getFilterGroups')->willReturn([]); - $this->model->getList($searchCriteriaMock); - } - - /** - * Test get list with no such entity exception - * - * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedExceptionMessage No such entity with attributeSetId = filter - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @return void - */ - public function testGetListWithNoSuchEntityException() - { - $attributeSetId = 'filter'; - $searchCriteriaMock = $this->createMock(\Magento\Framework\Api\SearchCriteriaInterface::class); - $filterGroupMock = $this->createMock(\Magento\Framework\Api\Search\FilterGroup::class); - $filterInterfaceMock = $this->createMock(\Magento\Framework\Api\Filter::class); - - $searchCriteriaMock->expects($this->once())->method('getFilterGroups')->willReturn([$filterGroupMock]); - - $filterGroupMock->expects($this->once())->method('getFilters')->willReturn([$filterInterfaceMock]); - $filterInterfaceMock->expects($this->once())->method('getField')->willReturn('attribute_set_id'); - $filterInterfaceMock->expects($this->once())->method('getValue')->willReturn($attributeSetId); - - $searchCriteriaMock->expects($this->once())->method('getFilterGroups')->willReturn([]); - $this->setRepositoryMock->expects($this->once()) - ->method('get') - ->with($attributeSetId) - ->willThrowException(new \Exception()); - $this->model->getList($searchCriteriaMock); - } - /** * Test get * diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/AbstractEntityTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/AbstractEntityTest.php index 682907e9c0a23a829f54413f5ee77b14aef5847d..67899dc3902ebd362b8083a827afc15dfd0b85bd 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/AbstractEntityTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/AbstractEntityTest.php @@ -320,19 +320,29 @@ class AbstractEntityTest extends \PHPUnit\Framework\TestCase [ 'test_attr', $attributeSetId, - ['test_attr' => 'test_attr', 'attribute_set_id' => $attributeSetId, 'entity_id' => null], + [ + 'test_attr' => 'test_attr', + 'attribute_set_id' => $attributeSetId, + 'entity_id' => null, + 'store_id' => 1 + ], null, ], [ 'test_attr', $attributeSetId, - ['test_attr' => 'test_attr', 'attribute_set_id' => $attributeSetId, 'entity_id' => 12345], + [ + 'test_attr' => 'test_attr', + 'attribute_set_id' => $attributeSetId, + 'entity_id' => 12345, + 'store_id' => 1 + ], ['test_attr' => 'test_attr'] ], [ 'test_attr', $attributeSetId, - ['test_attr' => '99.99', 'attribute_set_id' => $attributeSetId, 'entity_id' => 12345], + ['test_attr' => '99.99', 'attribute_set_id' => $attributeSetId, 'entity_id' => 12345, 'store_id' => 1], ['test_attr' => '99.9900'] ] ]; diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeLoaderTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeLoaderTest.php index 3cfc3dafda3f52ff96350e05210a677728169fa6..3370695b47d7e6e1c6e6c32a31071ebce4e37987 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeLoaderTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/AttributeLoaderTest.php @@ -78,7 +78,7 @@ class AttributeLoaderTest extends \PHPUnit\Framework\TestCase $attributeMock->expects($this->once())->method('setIsGlobal')->with(1)->willReturnSelf(); $attributeMock->expects($this->once())->method('setEntityType')->with($this->entityTypeMock)->willReturnSelf(); $attributeMock->expects($this->once())->method('setEntityTypeId')->with($entityTypeId)->willReturnSelf(); - $this->entityMock->expects($this->once())->method('addAttribute')->with($attributeMock)->willReturnSelf(); + $this->entityMock->expects($this->once())->method('addAttributeByScope')->willReturnSelf(); $this->attributeLoader->loadAllAttributes($this->entityMock, $dataObject); } @@ -102,8 +102,7 @@ class AttributeLoaderTest extends \PHPUnit\Framework\TestCase ->method('getEntityAttributes')->willReturn($attributeCodes); $this->entityMock->expects($this->once())->method('getDefaultAttributes')->willReturn($defaultAttributes); $this->entityMock->expects($this->once())->method('unsetAttributes')->willReturnSelf(); - $this->entityMock->expects($this->atLeastOnce()) - ->method('addAttribute')->with($attributeMock)->willReturnSelf(); + $this->entityMock->expects($this->atLeastOnce())->method('addAttributeByScope')->willReturnSelf(); $this->objectManagerMock->expects($this->never())->method('create'); $this->attributeLoader->loadAllAttributes($this->entityMock, $dataObject); } diff --git a/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js b/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js index 324881cdc502821ea4251d43bc0ef24abce95d1c..cd6292b39e98921e649b3afc4bb3965063129725 100644 --- a/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js +++ b/app/code/Magento/GoogleAnalytics/view/frontend/web/js/google-analytics.js @@ -51,10 +51,9 @@ define([ if (config.pageTrackingData.isAnonymizedIpActive) { ga('set', 'anonymizeIp', true); } - ga('send', 'pageview' + config.pageTrackingData.optPageUrl); // Process orders data - if (config.ordersTrackingData) { + if (config.ordersTrackingData.length) { ga('require', 'ec', 'ec.js'); //Set currency code @@ -75,6 +74,9 @@ define([ } ga('send', 'pageview'); + }else{ + // Process Data if not orders + ga('send', 'pageview' + config.pageTrackingData.optPageUrl); } } } diff --git a/app/code/Magento/Indexer/Model/Message/Invalid.php b/app/code/Magento/Indexer/Model/Message/Invalid.php index e13a5709e2362ce73206e64e37ec1ce0e72d7f26..5a3f879b0ad8033d380c86fd4f0c3da33335a6c7 100644 --- a/app/code/Magento/Indexer/Model/Message/Invalid.php +++ b/app/code/Magento/Indexer/Model/Message/Invalid.php @@ -71,7 +71,7 @@ class Invalid implements \Magento\Framework\Notification\MessageInterface return __( 'One or more <a href="%1">indexers are invalid</a>. Make sure your <a href="%2" target="_blank">Magento cron job</a> is running.', $url, - 'http://devdocs.magento.com/guides/v2.0/config-guide/cli/config-cli-subcommands-cron.html#config-cli-cron-bkg' + 'http://devdocs.magento.com/guides/v2.2/config-guide/cli/config-cli-subcommands-cron.html#create-or-remove-the-magento-crontab' ); //@codingStandardsIgnoreEnd } diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index cc143fdc52e3b4466be0a293b67d7fb85f31ec1e..8f29798472f19c11178f4480c5272d2b4638b55d 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -604,14 +604,20 @@ class Subscriber extends \Magento\Framework\Model\AbstractModel $this->save(); $sendSubscription = $sendInformationEmail; - if ($sendSubscription === null xor $sendSubscription) { + if ($sendSubscription === null xor $sendSubscription && $this->isStatusChanged()) { try { - if ($isConfirmNeed) { - $this->sendConfirmationRequestEmail(); - } elseif ($this->isStatusChanged() && $status == self::STATUS_UNSUBSCRIBED) { - $this->sendUnsubscriptionEmail(); - } elseif ($this->isStatusChanged() && $status == self::STATUS_SUBSCRIBED) { - $this->sendConfirmationSuccessEmail(); + switch ($status) { + case self::STATUS_UNSUBSCRIBED: + $this->sendUnsubscriptionEmail(); + break; + case self::STATUS_SUBSCRIBED: + $this->sendConfirmationSuccessEmail(); + break; + case self::STATUS_NOT_ACTIVE: + if ($isConfirmNeed) { + $this->sendConfirmationRequestEmail(); + } + break; } } catch (MailException $e) { // If we are not able to send a new account email, this should be ignored @@ -633,6 +639,8 @@ class Subscriber extends \Magento\Framework\Model\AbstractModel $this->setStatus(self::STATUS_SUBSCRIBED) ->setStatusChanged(true) ->save(); + + $this->sendConfirmationSuccessEmail(); return true; } diff --git a/app/code/Magento/Newsletter/Setup/UpgradeSchema.php b/app/code/Magento/Newsletter/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000000000000000000000000000000..e7ce898de83a3e04fa3369b963b22137b480243d --- /dev/null +++ b/app/code/Magento/Newsletter/Setup/UpgradeSchema.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Newsletter\Setup; + +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Setup\UpgradeSchemaInterface; + +/** + * Upgrade the Newsletter module DB scheme + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * {@inheritdoc} + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.1', '<')) { + $connection = $setup->getConnection(); + + $connection->addIndex( + $setup->getTable('newsletter_subscriber'), + $setup->getIdxName('newsletter_subscriber', ['subscriber_email']), + ['subscriber_email'] + ); + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php index 5a4032dc4dffd8361f385e0b88c7e35cc0727531..7dd96be11bcbe8b7db60f83fc19e77453f5e6eee 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriberTest.php @@ -341,6 +341,21 @@ class SubscriberTest extends \PHPUnit\Framework\TestCase $code = 111; $this->subscriber->setCode($code); $this->resource->expects($this->once())->method('save')->willReturnSelf(); + $storeModel = $this->getMockBuilder(\Magento\Store\Model\Store::class) + ->disableOriginalConstructor() + ->setMethods(['getId']) + ->getMock(); + $transport = $this->createMock(\Magento\Framework\Mail\TransportInterface::class); + $this->scopeConfig->expects($this->any())->method('getValue')->willReturn(true); + $this->transportBuilder->expects($this->once())->method('setTemplateIdentifier')->willReturnSelf(); + $this->transportBuilder->expects($this->once())->method('setTemplateOptions')->willReturnSelf(); + $this->transportBuilder->expects($this->once())->method('setTemplateVars')->willReturnSelf(); + $this->transportBuilder->expects($this->once())->method('setFrom')->willReturnSelf(); + $this->transportBuilder->expects($this->once())->method('addTo')->willReturnSelf(); + $this->storeManager->expects($this->any())->method('getStore')->willReturn($storeModel); + $storeModel->expects($this->any())->method('getId')->willReturn(1); + $this->transportBuilder->expects($this->once())->method('getTransport')->willReturn($transport); + $transport->expects($this->once())->method('sendMessage')->willReturnSelf(); $this->assertTrue($this->subscriber->confirm($code)); } diff --git a/app/code/Magento/Newsletter/etc/module.xml b/app/code/Magento/Newsletter/etc/module.xml index f338445225222a56ab82e2baebee390ccabefe36..5da16a9a3e9ba7b76c73cbeb9236dcd15a71a3b2 100644 --- a/app/code/Magento/Newsletter/etc/module.xml +++ b/app/code/Magento/Newsletter/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Newsletter" setup_version="2.0.0"> + <module name="Magento_Newsletter" setup_version="2.0.1"> <sequence> <module name="Magento_Store"/> <module name="Magento_Customer"/> diff --git a/app/code/Magento/Payment/Gateway/Command/GatewayCommand.php b/app/code/Magento/Payment/Gateway/Command/GatewayCommand.php index a6f9d4383918c92d9e7a55b0dee57963e9fa5f01..bb07408ad0e06a3857315879cc23f9bf25cca425 100644 --- a/app/code/Magento/Payment/Gateway/Command/GatewayCommand.php +++ b/app/code/Magento/Payment/Gateway/Command/GatewayCommand.php @@ -5,14 +5,13 @@ */ namespace Magento\Payment\Gateway\Command; -use Magento\Framework\Phrase; use Magento\Payment\Gateway\CommandInterface; +use Magento\Payment\Gateway\ErrorMapper\ErrorMessageMapperInterface; use Magento\Payment\Gateway\Http\ClientInterface; use Magento\Payment\Gateway\Http\TransferFactoryInterface; -use Magento\Payment\Gateway\Request; use Magento\Payment\Gateway\Request\BuilderInterface; -use Magento\Payment\Gateway\Response; use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Payment\Gateway\Validator\ResultInterface; use Magento\Payment\Gateway\Validator\ValidatorInterface; use Psr\Log\LoggerInterface; @@ -54,6 +53,11 @@ class GatewayCommand implements CommandInterface */ private $logger; + /** + * @var ErrorMessageMapperInterface + */ + private $errorMessageMapper; + /** * @param BuilderInterface $requestBuilder * @param TransferFactoryInterface $transferFactory @@ -61,6 +65,7 @@ class GatewayCommand implements CommandInterface * @param LoggerInterface $logger * @param HandlerInterface $handler * @param ValidatorInterface $validator + * @param ErrorMessageMapperInterface|null $errorMessageMapper */ public function __construct( BuilderInterface $requestBuilder, @@ -68,7 +73,8 @@ class GatewayCommand implements CommandInterface ClientInterface $client, LoggerInterface $logger, HandlerInterface $handler = null, - ValidatorInterface $validator = null + ValidatorInterface $validator = null, + ErrorMessageMapperInterface $errorMessageMapper = null ) { $this->requestBuilder = $requestBuilder; $this->transferFactory = $transferFactory; @@ -76,6 +82,7 @@ class GatewayCommand implements CommandInterface $this->handler = $handler; $this->validator = $validator; $this->logger = $logger; + $this->errorMessageMapper = $errorMessageMapper; } /** @@ -98,10 +105,7 @@ class GatewayCommand implements CommandInterface array_merge($commandSubject, ['response' => $response]) ); if (!$result->isValid()) { - $this->logExceptions($result->getFailsDescription()); - throw new CommandException( - __('Transaction has been declined. Please try again later.') - ); + $this->processErrors($result); } } @@ -114,13 +118,33 @@ class GatewayCommand implements CommandInterface } /** - * @param Phrase[] $fails - * @return void + * Tries to map error messages from validation result and logs processed message. + * Throws an exception with mapped message or default error. + * + * @param ResultInterface $result + * @throws CommandException */ - private function logExceptions(array $fails) + private function processErrors(ResultInterface $result) { - foreach ($fails as $failPhrase) { - $this->logger->critical((string) $failPhrase); + $messages = []; + foreach ($result->getFailsDescription() as $failPhrase) { + $message = (string) $failPhrase; + + // error messages mapper can be not configured if payment method doesn't have custom error messages. + if ($this->errorMessageMapper !== null) { + $mapped = (string) $this->errorMessageMapper->getMessage($message); + if (!empty($mapped)) { + $messages[] = $mapped; + $message = $mapped; + } + } + $this->logger->critical('Payment Error: ' . $message); } + + throw new CommandException( + !empty($messages) + ? __(implode(PHP_EOL, $messages)) + : __('Transaction has been declined. Please try again later.') + ); } } diff --git a/app/code/Magento/Payment/Gateway/ErrorMapper/ErrorMessageMapper.php b/app/code/Magento/Payment/Gateway/ErrorMapper/ErrorMessageMapper.php new file mode 100644 index 0000000000000000000000000000000000000000..c5759d41bf4d73f5f674efcc780128be72b699d5 --- /dev/null +++ b/app/code/Magento/Payment/Gateway/ErrorMapper/ErrorMessageMapper.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Gateway\ErrorMapper; + +use Magento\Framework\Config\DataInterface; + +/** + * This class can be used for payment integrations which can validate different type of + * error messages per one request. + * For example, during authorization payment operation the payment integration can validate error messages + * related to credit card details and customer address data. + * In that case, this implementation can be extended via di.xml and configured with appropriate mappers. + */ +class ErrorMessageMapper implements ErrorMessageMapperInterface +{ + /** + * @var DataInterface + */ + private $messageMapping; + + /** + * @param DataInterface $messageMapping + */ + public function __construct(DataInterface $messageMapping) + { + $this->messageMapping = $messageMapping; + } + + /** + * @inheritdoc + */ + public function getMessage(string $code) + { + $message = $this->messageMapping->get($code); + return $message ? __($message) : null; + } +} diff --git a/app/code/Magento/Payment/Gateway/ErrorMapper/ErrorMessageMapperInterface.php b/app/code/Magento/Payment/Gateway/ErrorMapper/ErrorMessageMapperInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..077226fd9a062b5a8fb148b6c30b0d07c375092e --- /dev/null +++ b/app/code/Magento/Payment/Gateway/ErrorMapper/ErrorMessageMapperInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Gateway\ErrorMapper; + +use Magento\Framework\Phrase; + +/** + * Interface to provide customization for payment validation errors. + */ +interface ErrorMessageMapperInterface +{ + /** + * Returns customized error message by provided code. + * If message not found `null` will be returned. + * + * @param string $code + * @return Phrase|null + */ + public function getMessage(string $code); +} diff --git a/app/code/Magento/Payment/Gateway/ErrorMapper/MappingData.php b/app/code/Magento/Payment/Gateway/ErrorMapper/MappingData.php new file mode 100644 index 0000000000000000000000000000000000000000..8ae29c7a729e745f1ec36e0b979f5214c05c2a71 --- /dev/null +++ b/app/code/Magento/Payment/Gateway/ErrorMapper/MappingData.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Gateway\ErrorMapper; + +use Magento\Framework\Config\Data\Scoped; + +/** + * Extends Scoped class to override `_scopePriorityScheme` property. + * It allows to load and merge config files from `global` scope and current scope to a single structure. + */ +class MappingData extends Scoped +{ + /** + * @inheritdoc + */ + protected $_scopePriorityScheme = ['global']; +} diff --git a/app/code/Magento/Payment/Gateway/ErrorMapper/NullMappingData.php b/app/code/Magento/Payment/Gateway/ErrorMapper/NullMappingData.php new file mode 100644 index 0000000000000000000000000000000000000000..6b3e592d984ce4007c2bfb1bbf6bc0aafde04aa0 --- /dev/null +++ b/app/code/Magento/Payment/Gateway/ErrorMapper/NullMappingData.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Gateway\ErrorMapper; + +use Magento\Framework\Config\DataInterface; + +/** + * Stub implementation of DataInterface which is used by default for ErrorMessageMapper, because + * each payment method should provide own mapping data source. + */ +class NullMappingData implements DataInterface +{ + /** + * @inheritdoc + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function get($path = null, $default = null) + { + return null; + } + + /** + * @inheritdoc + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function merge(array $config) + { + } +} diff --git a/app/code/Magento/Payment/Gateway/ErrorMapper/XmlToArrayConverter.php b/app/code/Magento/Payment/Gateway/ErrorMapper/XmlToArrayConverter.php new file mode 100644 index 0000000000000000000000000000000000000000..590a114e2222167911fcf32e5e97d52eff413652 --- /dev/null +++ b/app/code/Magento/Payment/Gateway/ErrorMapper/XmlToArrayConverter.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Payment\Gateway\ErrorMapper; + +use Magento\Framework\Config\ConverterInterface; + +/** + * Reads xml in `<message code="code">message</message>` format and converts it to [code => message] array format. + */ +class XmlToArrayConverter implements ConverterInterface +{ + /** + * @inheritdoc + */ + public function convert($source) + { + $result = []; + $messageList = $source->getElementsByTagName('message'); + foreach ($messageList as $messageNode) { + $result[(string) $messageNode->getAttribute('code')] = (string) $messageNode->nodeValue; + } + return $result; + } +} diff --git a/app/code/Magento/Payment/Test/Unit/Gateway/Command/GatewayCommandTest.php b/app/code/Magento/Payment/Test/Unit/Gateway/Command/GatewayCommandTest.php index df8bdc9bca54be6bef9758e8c25ff5597ea039cf..d17a7f302f31b098ad06db77518501a806122b4a 100644 --- a/app/code/Magento/Payment/Test/Unit/Gateway/Command/GatewayCommandTest.php +++ b/app/code/Magento/Payment/Test/Unit/Gateway/Command/GatewayCommandTest.php @@ -6,11 +6,15 @@ namespace Magento\Payment\Test\Unit\Gateway\Command; use Magento\Payment\Gateway\Command\GatewayCommand; +use Magento\Payment\Gateway\ErrorMapper\ErrorMessageMapperInterface; use Magento\Payment\Gateway\Http\ClientInterface; use Magento\Payment\Gateway\Http\TransferFactoryInterface; +use Magento\Payment\Gateway\Http\TransferInterface; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Gateway\Response\HandlerInterface; +use Magento\Payment\Gateway\Validator\ResultInterface; use Magento\Payment\Gateway\Validator\ValidatorInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; use Psr\Log\LoggerInterface; /** @@ -18,175 +22,176 @@ use Psr\Log\LoggerInterface; */ class GatewayCommandTest extends \PHPUnit\Framework\TestCase { - /** @var GatewayCommand */ - protected $command; + /** + * @var GatewayCommand + */ + private $command; /** - * @var BuilderInterface|\PHPUnit_Framework_MockObject_MockObject + * @var BuilderInterface|MockObject */ - protected $requestBuilderMock; + private $requestBuilder; /** - * @var TransferFactoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var TransferFactoryInterface|MockObject */ - protected $transferFactoryMock; + private $transferFactory; /** - * @var ClientInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ClientInterface|MockObject */ - protected $clientMock; + private $client; /** - * @var HandlerInterface|\PHPUnit_Framework_MockObject_MockObject + * @var HandlerInterface|MockObject */ - protected $responseHandlerMock; + private $responseHandler; /** - * @var ValidatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var ValidatorInterface|MockObject */ - protected $validatorMock; + private $validator; /** - * @var LoggerInterface |\PHPUnit_Framework_MockObject_MockObject + * @var LoggerInterface|MockObject */ private $logger; + /** + * @var ErrorMessageMapperInterface|MockObject + */ + private $errorMessageMapper; + protected function setUp() { - $this->requestBuilderMock = $this->createMock( - BuilderInterface::class - ); - $this->transferFactoryMock = $this->createMock( - TransferFactoryInterface::class - ); - $this->clientMock = $this->createMock( - ClientInterface::class - ); - $this->responseHandlerMock = $this->createMock( - HandlerInterface::class - ); - $this->validatorMock = $this->createMock( - ValidatorInterface::class - ); + $this->requestBuilder = $this->createMock(BuilderInterface::class); + $this->transferFactory = $this->createMock(TransferFactoryInterface::class); + $this->client = $this->createMock(ClientInterface::class); + $this->responseHandler = $this->createMock(HandlerInterface::class); + $this->validator = $this->createMock(ValidatorInterface::class); $this->logger = $this->createMock(LoggerInterface::class); + $this->errorMessageMapper = $this->createMock(ErrorMessageMapperInterface::class); $this->command = new GatewayCommand( - $this->requestBuilderMock, - $this->transferFactoryMock, - $this->clientMock, + $this->requestBuilder, + $this->transferFactory, + $this->client, $this->logger, - $this->responseHandlerMock, - $this->validatorMock + $this->responseHandler, + $this->validator, + $this->errorMessageMapper ); } public function testExecute() { $commandSubject = ['authorize']; - $request = [ - 'request_field1' => 'request_value1', - 'request_field2' => 'request_value2' - ]; - $response = ['response_field1' => 'response_value1']; - $validationResult = $this->getMockBuilder( - \Magento\Payment\Gateway\Validator\ResultInterface::class - ) - ->getMockForAbstractClass(); + $this->processRequest($commandSubject, true); - $transferO = $this->getMockBuilder( - \Magento\Payment\Gateway\Http\TransferInterface::class - ) - ->getMockForAbstractClass(); + $this->responseHandler->method('handle') + ->with($commandSubject, ['response_field1' => 'response_value1']); - $this->requestBuilderMock->expects(static::once()) - ->method('build') - ->with($commandSubject) - ->willReturn($request); + $this->command->execute($commandSubject); + } - $this->transferFactoryMock->expects(static::once()) - ->method('create') - ->with($request) - ->willReturn($transferO); + /** + * Checks a case when request fails. + * + * @expectedException \Magento\Payment\Gateway\Command\CommandException + * @expectedExceptionMessage Transaction has been declined. Please try again later. + */ + public function testExecuteValidationFail() + { + $commandSubject = ['authorize']; + $validationFailures = [ + __('Failure #1'), + __('Failure #2'), + ]; - $this->clientMock->expects(static::once()) - ->method('placeRequest') - ->with($transferO) - ->willReturn($response); - $this->validatorMock->expects(static::once()) - ->method('validate') - ->with(array_merge($commandSubject, ['response' =>$response])) - ->willReturn($validationResult); - $validationResult->expects(static::once()) - ->method('isValid') - ->willReturn(true); + $this->processRequest($commandSubject, false, $validationFailures); - $this->responseHandlerMock->expects(static::once()) - ->method('handle') - ->with($commandSubject, $response); + $this->logger->expects(self::exactly(count($validationFailures))) + ->method('critical') + ->withConsecutive( + [self::equalTo('Payment Error: ' . $validationFailures[0])], + [self::equalTo('Payment Error: ' . $validationFailures[1])] + ); $this->command->execute($commandSubject); } - public function testExecuteValidationFail() + /** + * Checks a case when request fails and response errors are mapped. + * + * @expectedException \Magento\Payment\Gateway\Command\CommandException + * @expectedExceptionMessage Failure Mapped + */ + public function testExecuteValidationFailWithMappedErrors() { - $this->expectException( - \Magento\Payment\Gateway\Command\CommandException::class - ); - $commandSubject = ['authorize']; - $request = [ - 'request_field1' => 'request_value1', - 'request_field2' => 'request_value2' - ]; - $response = ['response_field1' => 'response_value1']; $validationFailures = [ __('Failure #1'), __('Failure #2'), ]; - $validationResult = $this->getMockBuilder( - \Magento\Payment\Gateway\Validator\ResultInterface::class - ) - ->getMockForAbstractClass(); - $transferO = $this->getMockBuilder( - \Magento\Payment\Gateway\Http\TransferInterface::class - ) + $this->processRequest($commandSubject, false, $validationFailures); + + $this->errorMessageMapper->method('getMessage') + ->willReturnMap( + [ + ['Failure #1', 'Failure Mapped'], + ['Failure #2', null] + ] + ); + + $this->logger->expects(self::exactly(count($validationFailures))) + ->method('critical') + ->withConsecutive( + [self::equalTo('Payment Error: Failure Mapped')], + [self::equalTo('Payment Error: Failure #2')] + ); + + $this->command->execute($commandSubject); + } + + /** + * Performs command actions like request, response and validation. + * + * @param array $commandSubject + * @param bool $validationResult + * @param array $validationFailures + */ + private function processRequest(array $commandSubject, bool $validationResult, array $validationFailures = []) + { + $request = [ + 'request_field1' => 'request_value1', + 'request_field2' => 'request_value2' + ]; + $response = ['response_field1' => 'response_value1']; + $transferO = $this->getMockBuilder(TransferInterface::class) ->getMockForAbstractClass(); - $this->requestBuilderMock->expects(static::once()) - ->method('build') + $this->requestBuilder->method('build') ->with($commandSubject) ->willReturn($request); - $this->transferFactoryMock->expects(static::once()) - ->method('create') + $this->transferFactory->method('create') ->with($request) ->willReturn($transferO); - $this->clientMock->expects(static::once()) - ->method('placeRequest') + $this->client->method('placeRequest') ->with($transferO) ->willReturn($response); - $this->validatorMock->expects(static::once()) - ->method('validate') - ->with(array_merge($commandSubject, ['response' =>$response])) - ->willReturn($validationResult); - $validationResult->expects(static::once()) - ->method('isValid') - ->willReturn(false); - $validationResult->expects(static::once()) - ->method('getFailsDescription') - ->willReturn( - $validationFailures - ); - $this->logger->expects(static::exactly(count($validationFailures))) - ->method('critical') - ->withConsecutive( - [$validationFailures[0]], - [$validationFailures[1]] - ); + $result = $this->getMockBuilder(ResultInterface::class) + ->getMockForAbstractClass(); - $this->command->execute($commandSubject); + $this->validator->method('validate') + ->with(array_merge($commandSubject, ['response' => $response])) + ->willReturn($result); + $result->method('isValid') + ->willReturn($validationResult); + $result->method('getFailsDescription') + ->willReturn($validationFailures); } } diff --git a/app/code/Magento/Payment/Test/Unit/Model/Method/FactoryTest.php b/app/code/Magento/Payment/Test/Unit/Model/Method/FactoryTest.php deleted file mode 100644 index f0cb19ef0fa0f27343a282e3cbc405e44696e19f..0000000000000000000000000000000000000000 --- a/app/code/Magento/Payment/Test/Unit/Model/Method/FactoryTest.php +++ /dev/null @@ -1,89 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Payment\Test\Unit\Model\Method; - -class FactoryTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Framework\ObjectManagerInterface|PHPUnit_Framework_MockObject_MockObject - */ - protected $_objectManagerMock; - - /** - * @var \Magento\Payment\Model\Method\Factory - */ - protected $_factory; - - protected function setUp() - { - $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->_objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); - $this->_factory = $objectManagerHelper->getObject( - \Magento\Payment\Model\Method\Factory::class, - ['objectManager' => $this->_objectManagerMock] - ); - } - - public function testCreateMethod() - { - $className = \Magento\Payment\Model\Method\AbstractMethod::class; - $methodMock = $this->createMock($className); - $this->_objectManagerMock->expects( - $this->once() - )->method( - 'create' - )->with( - $className, - [] - )->will( - $this->returnValue($methodMock) - ); - - $this->assertEquals($methodMock, $this->_factory->create($className)); - } - - public function testCreateMethodWithArguments() - { - $className = \Magento\Payment\Model\Method\AbstractMethod::class; - $data = ['param1', 'param2']; - $methodMock = $this->createMock($className); - $this->_objectManagerMock->expects( - $this->once() - )->method( - 'create' - )->with( - $className, - $data - )->will( - $this->returnValue($methodMock) - ); - - $this->assertEquals($methodMock, $this->_factory->create($className, $data)); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage WrongClass class doesn't implement \Magento\Payment\Model\MethodInterface - */ - public function testWrongTypeException() - { - $className = 'WrongClass'; - $methodMock = $this->createMock($className); - $this->_objectManagerMock->expects( - $this->once() - )->method( - 'create' - )->with( - $className, - [] - )->will( - $this->returnValue($methodMock) - ); - - $this->_factory->create($className); - } -} diff --git a/app/code/Magento/Payment/Test/Unit/Model/Method/Specification/FactoryTest.php b/app/code/Magento/Payment/Test/Unit/Model/Method/Specification/FactoryTest.php deleted file mode 100644 index 9bdc90829f6feb92a22f1458a88d046b9a18cc2e..0000000000000000000000000000000000000000 --- a/app/code/Magento/Payment/Test/Unit/Model/Method/Specification/FactoryTest.php +++ /dev/null @@ -1,71 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Payment\Test\Unit\Model\Method\Specification; - -/** - * Factory Test - */ -class FactoryTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Payment\Model\Method\Specification\Factory - */ - protected $factory; - - protected function setUp() - { - $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class); - - $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->factory = $objectManagerHelper->getObject( - \Magento\Payment\Model\Method\Specification\Factory::class, - ['objectManager' => $this->objectManagerMock] - ); - } - - public function testCreateMethod() - { - $className = \Magento\Payment\Model\Method\SpecificationInterface::class; - $methodMock = $this->createMock($className); - $this->objectManagerMock->expects( - $this->once() - )->method( - 'get' - )->with( - $className - )->will( - $this->returnValue($methodMock) - ); - - $this->assertEquals($methodMock, $this->factory->create($className)); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Specification must implement SpecificationInterface - */ - public function testWrongTypeException() - { - $className = 'WrongClass'; - $methodMock = $this->createMock($className); - $this->objectManagerMock->expects( - $this->once() - )->method( - 'get' - )->with( - $className - )->will( - $this->returnValue($methodMock) - ); - - $this->factory->create($className); - } -} diff --git a/app/code/Magento/Payment/etc/di.xml b/app/code/Magento/Payment/etc/di.xml index e2de2244bff89b3215f45c37da13860085c3b014..e62c5e1c1cd4d3de0ed439a62474590866a50de9 100644 --- a/app/code/Magento/Payment/etc/di.xml +++ b/app/code/Magento/Payment/etc/di.xml @@ -12,6 +12,7 @@ <preference for="Magento\Payment\Gateway\ConfigFactoryInterface" type="Magento\Payment\Gateway\Config\ConfigFactory" /> <preference for="Magento\Payment\Gateway\Command\CommandManagerPoolInterface" type="Magento\Payment\Gateway\Command\CommandManagerPool" /> <preference for="Magento\Payment\Gateway\Data\PaymentDataObjectFactoryInterface" type="Magento\Payment\Gateway\Data\PaymentDataObjectFactory" /> + <preference for="Magento\Payment\Gateway\ErrorMapper\ErrorMessageMapperInterface" type="Magento\Payment\Gateway\ErrorMapper\ErrorMessageMapper" /> <type name="Magento\Payment\Model\Config\Reader"> <arguments> @@ -36,4 +37,29 @@ <argument name="config" xsi:type="object">Magento\Payment\Gateway\Config\Config</argument> </arguments> </type> + + <virtualType name="Magento\Payment\Gateway\ErrorMapper\VirtualSchemaLocator" type="Magento\Framework\Config\GenericSchemaLocator"> + <arguments> + <argument name="moduleName" xsi:type="string">Magento_Payment</argument> + <argument name="schema" xsi:type="string">error_mapping.xsd</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Payment\Gateway\ErrorMapper\VirtualConfigReader" type="Magento\Framework\Config\Reader\Filesystem"> + <arguments> + <argument name="converter" xsi:type="object">Magento\Payment\Gateway\ErrorMapper\XmlToArrayConverter</argument> + <argument name="schemaLocator" xsi:type="object">Magento\Payment\Gateway\ErrorMapper\VirtualSchemaLocator</argument> + <argument name="fileName" xsi:type="string">error_mapping.xml</argument> + </arguments> + </virtualType> + <type name="Magento\Payment\Gateway\ErrorMapper\MappingData"> + <arguments> + <argument name="reader" xsi:type="object">Magento\Payment\Gateway\ErrorMapper\VirtualConfigReader</argument> + <argument name="cacheId" xsi:type="string">payment_error_mapper</argument> + </arguments> + </type> + <type name="Magento\Payment\Gateway\ErrorMapper\ErrorMessageMapper"> + <arguments> + <argument name="messageMapping" xsi:type="object">Magento\Payment\Gateway\ErrorMapper\NullMappingData</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Payment/etc/error_mapping.xsd b/app/code/Magento/Payment/etc/error_mapping.xsd new file mode 100644 index 0000000000000000000000000000000000000000..97f3c181beb37abf7c11d11657a4e63ce012c61c --- /dev/null +++ b/app/code/Magento/Payment/etc/error_mapping.xsd @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> + + <xs:element name="mapping"> + <xs:complexType> + <xs:sequence> + <xs:element minOccurs="1" maxOccurs="1" name="message_list" type="message_list" /> + </xs:sequence> + </xs:complexType> + </xs:element> + + <xs:complexType name="message_list"> + <xs:sequence> + <xs:element minOccurs="0" maxOccurs="unbounded" name="message" type="message" /> + </xs:sequence> + </xs:complexType> + + <xs:complexType name="message"> + <xs:simpleContent> + <xs:extension base="xs:string"> + <xs:attribute name="code" type="xs:string" use="required" /> + <xs:attribute name="translate" type="xs:boolean" use="optional" /> + </xs:extension> + </xs:simpleContent> + </xs:complexType> +</xs:schema> diff --git a/app/code/Magento/Payment/view/adminhtml/templates/info/substitution.phtml b/app/code/Magento/Payment/view/adminhtml/templates/info/substitution.phtml index ad24b113ffdead50ef0443fa609123b42b5e063f..582b8ca8a24bec1d5646812a38d77ee61d39f2ff 100644 --- a/app/code/Magento/Payment/view/adminhtml/templates/info/substitution.phtml +++ b/app/code/Magento/Payment/view/adminhtml/templates/info/substitution.phtml @@ -10,6 +10,8 @@ */ ?> <div> - <?php $block->escapeHtml($block->getMethod()->getTitle());?> + <?= $block->getMethod()->getTitle() + ? $block->escapeHtml($block->getMethod()->getTitle()) + : $block->escapeHtml(__('Payment method')); ?> <?= $block->escapeHtml(__(' is not available. You still can process offline actions.')) ?> </div> diff --git a/app/code/Magento/Paypal/Model/Express.php b/app/code/Magento/Paypal/Model/Express.php index 8ba8adcede511745f53fa94bbc4632920e37f529..accb22b265335de00797e66a36252537eb902628 100644 --- a/app/code/Magento/Paypal/Model/Express.php +++ b/app/code/Magento/Paypal/Model/Express.php @@ -669,7 +669,7 @@ class Express extends \Magento\Payment\Model\Method\AbstractMethod public function assignData(\Magento\Framework\DataObject $data) { parent::assignData($data); - + $additionalData = $data->getData(PaymentInterface::KEY_ADDITIONAL_DATA); if (!is_array($additionalData)) { @@ -677,6 +677,11 @@ class Express extends \Magento\Payment\Model\Method\AbstractMethod } foreach ($additionalData as $key => $value) { + // Skip extension attributes + if ($key === \Magento\Framework\Api\ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY) { + continue; + } + $this->getInfoInstance()->setAdditionalInformation($key, $value); } return $this; diff --git a/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php b/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php index 6a2d33d010190fd0d659d28a447753c3761e94b3..1b8c33622e784154908d6df12b3f6592f33ca68a 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/ExpressTest.php @@ -161,12 +161,21 @@ class ExpressTest extends \PHPUnit\Framework\TestCase { $transportValue = 'something'; + $extensionAttribute = $this->getMockForAbstractClass( + \Magento\Quote\Api\Data\PaymentExtensionInterface::class, + [], + '', + false, + false + ); + $data = new DataObject( [ PaymentInterface::KEY_ADDITIONAL_DATA => [ Express\Checkout::PAYMENT_INFO_TRANSPORT_BILLING_AGREEMENT => $transportValue, Express\Checkout::PAYMENT_INFO_TRANSPORT_PAYER_ID => $transportValue, - Express\Checkout::PAYMENT_INFO_TRANSPORT_TOKEN => $transportValue + Express\Checkout::PAYMENT_INFO_TRANSPORT_TOKEN => $transportValue, + \Magento\Framework\Api\ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY => $extensionAttribute ] ] ); diff --git a/app/code/Magento/Paypal/view/frontend/web/js/action/set-payment-method.js b/app/code/Magento/Paypal/view/frontend/web/js/action/set-payment-method.js index a994f9defd583823f6da37557f675f94eee13cbd..63e34437c6f90c6b663b3eaf0bd1da306ffec3d3 100644 --- a/app/code/Magento/Paypal/view/frontend/web/js/action/set-payment-method.js +++ b/app/code/Magento/Paypal/view/frontend/web/js/action/set-payment-method.js @@ -4,48 +4,12 @@ */ define([ - 'jquery', 'Magento_Checkout/js/model/quote', - 'Magento_Checkout/js/model/url-builder', - 'mage/storage', - 'Magento_Checkout/js/model/error-processor', - 'Magento_Customer/js/model/customer', - 'Magento_Checkout/js/model/full-screen-loader' -], function ($, quote, urlBuilder, storage, errorProcessor, customer, fullScreenLoader) { + 'Magento_Checkout/js/action/set-payment-information' +], function (quote, setPaymentInformation) { 'use strict'; return function (messageContainer) { - var serviceUrl, - payload, - paymentData = quote.paymentMethod(); - - /** - * Checkout for guest and registered customer. - */ - if (!customer.isLoggedIn()) { - serviceUrl = urlBuilder.createUrl('/guest-carts/:cartId/set-payment-information', { - cartId: quote.getQuoteId() - }); - payload = { - cartId: quote.getQuoteId(), - email: quote.guestEmail, - paymentMethod: paymentData - }; - } else { - serviceUrl = urlBuilder.createUrl('/carts/mine/set-payment-information', {}); - payload = { - cartId: quote.getQuoteId(), - paymentMethod: paymentData - }; - } - fullScreenLoader.startLoader(); - - return storage.post( - serviceUrl, JSON.stringify(payload) - ).fail(function (response) { - errorProcessor.process(response, messageContainer); - }).always(function () { - fullScreenLoader.stopLoader(); - }); + return setPaymentInformation(messageContainer, quote.paymentMethod()); }; }); diff --git a/app/code/Magento/ProductAlert/Controller/Add/TestObserver.php b/app/code/Magento/ProductAlert/Controller/Add/TestObserver.php deleted file mode 100644 index 74f03220e59d381e0f8af2e788e6484e0d7fdbc9..0000000000000000000000000000000000000000 --- a/app/code/Magento/ProductAlert/Controller/Add/TestObserver.php +++ /dev/null @@ -1,23 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ProductAlert\Controller\Add; - -use Magento\ProductAlert\Controller\Add as AddController; -use Magento\Framework\DataObject; - -class TestObserver extends AddController -{ - /** - * @return void - */ - public function execute() - { - $object = new DataObject(); - /** @var \Magento\ProductAlert\Model\Observer $observer */ - $observer = $this->_objectManager->get(\Magento\ProductAlert\Model\Observer::class); - $observer->process($object); - } -} diff --git a/app/code/Magento/Quote/Model/Quote/Item.php b/app/code/Magento/Quote/Model/Quote/Item.php index d8177ddfe5236500cb5d2099bfab2b6d47ee9391..fe6d712500bcd7e6d61a782fe757dc78f67c96e5 100644 --- a/app/code/Magento/Quote/Model/Quote/Item.php +++ b/app/code/Magento/Quote/Model/Quote/Item.php @@ -745,6 +745,9 @@ class Item extends \Magento\Quote\Model\Quote\Item\AbstractItem implements \Mage unset($this->_options[$index]); unset($this->_optionsByCode[$option->getCode()]); } else { + if (!$option->getItem() || !$option->getItem()->getId()) { + $option->setItem($this); + } $option->save(); } } diff --git a/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php b/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php index 3113721f8a59781406107f23a54f832b11fb976b..38bfcbf1d30ca445785faee62117cd4dfa57957f 100644 --- a/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php +++ b/app/code/Magento/Quote/Model/Quote/Validator/MinimumOrderAmount/ValidationMessage.php @@ -19,22 +19,32 @@ class ValidationMessage /** * @var \Magento\Framework\Locale\CurrencyInterface + * @deprecated since 101.0.0 */ private $currency; + /** + * @var \Magento\Framework\Pricing\Helper\Data + */ + private $priceHelper; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\Locale\CurrencyInterface $currency + * @param \Magento\Framework\Pricing\Helper\Data $priceHelper */ public function __construct( \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\Locale\CurrencyInterface $currency + \Magento\Framework\Locale\CurrencyInterface $currency, + \Magento\Framework\Pricing\Helper\Data $priceHelper = null ) { $this->scopeConfig = $scopeConfig; $this->storeManager = $storeManager; $this->currency = $currency; + $this->priceHelper = $priceHelper ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Pricing\Helper\Data::class); } /** @@ -50,13 +60,11 @@ class ValidationMessage \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); if (!$message) { - $currencyCode = $this->storeManager->getStore()->getCurrentCurrencyCode(); - $minimumAmount = $this->currency->getCurrency($currencyCode)->toCurrency( - $this->scopeConfig->getValue( - 'sales/minimum_order/amount', - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ) - ); + $minimumAmount = $this->priceHelper->currency($this->scopeConfig->getValue( + 'sales/minimum_order/amount', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ), true, false); + $message = __('Minimum order amount is %1', $minimumAmount); } else { //Added in order to address the issue: https://github.com/magento/magento2/issues/8287 diff --git a/app/code/Magento/Quote/Setup/UpgradeSchema.php b/app/code/Magento/Quote/Setup/UpgradeSchema.php index 1bb20a669bdf2693ce65f0ae97c6809da998de61..e9221895e18dc70a0d9698b77902627f5e6410ae 100644 --- a/app/code/Magento/Quote/Setup/UpgradeSchema.php +++ b/app/code/Magento/Quote/Setup/UpgradeSchema.php @@ -5,6 +5,7 @@ */ namespace Magento\Quote\Setup; +use Magento\Framework\DB\Ddl\Table; use Magento\Framework\Setup\UpgradeSchemaInterface; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\SchemaSetupInterface; @@ -40,7 +41,7 @@ class UpgradeSchema implements UpgradeSchemaInterface 'street', 'street', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 255, 'comment' => 'Street' ] @@ -61,7 +62,7 @@ class UpgradeSchema implements UpgradeSchemaInterface $setup->getTable('quote_address', self::$connectionName), 'shipping_method', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 120 ] ); @@ -72,33 +73,53 @@ class UpgradeSchema implements UpgradeSchemaInterface $setup->getTable('quote_address', self::$connectionName), 'firstname', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 255, ] )->modifyColumn( $setup->getTable('quote_address', self::$connectionName), 'middlename', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 40, ] )->modifyColumn( $setup->getTable('quote_address', self::$connectionName), 'lastname', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT, + 'type' => Table::TYPE_TEXT, 'length' => 255, ] )->modifyColumn( $setup->getTable('quote', self::$connectionName), 'updated_at', [ - 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP, + 'type' => Table::TYPE_TIMESTAMP, 'nullable' => false, - 'default' => \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT_UPDATE, + 'default' => Table::TIMESTAMP_INIT_UPDATE, ] ); } + if (version_compare($context->getVersion(), '2.0.7', '<')) { + $connection = $setup->getConnection(self::$connectionName); + $connection->modifyColumn( + $setup->getTable('quote_address', self::$connectionName), + 'telephone', + ['type' => Table::TYPE_TEXT, 'length' => 255] + )->modifyColumn( + $setup->getTable('quote_address', self::$connectionName), + 'fax', + ['type' => Table::TYPE_TEXT, 'length' => 255] + )->modifyColumn( + $setup->getTable('quote_address', self::$connectionName), + 'region', + ['type' => Table::TYPE_TEXT, 'length' => 255] + )->modifyColumn( + $setup->getTable('quote_address', self::$connectionName), + 'city', + ['type' => Table::TYPE_TEXT, 'length' => 255] + ); + } $setup->endSetup(); } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php index 64204ea1fb93dbeb35cf0b5f66bbd85632c787eb..272a4e3a4ba49206273330c8d864b262ea402bdf 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Validator/MinimumOrderAmount/ValidationMessageTest.php @@ -26,19 +26,27 @@ class ValidationMessageTest extends \PHPUnit\Framework\TestCase /** * @var \PHPUnit_Framework_MockObject_MockObject + * @deprecated since 101.0.0 */ private $currencyMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $priceHelperMock; + protected function setUp() { $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $this->currencyMock = $this->createMock(\Magento\Framework\Locale\CurrencyInterface::class); + $this->priceHelperMock = $this->createMock(\Magento\Framework\Pricing\Helper\Data::class); $this->model = new \Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage( $this->scopeConfigMock, $this->storeManagerMock, - $this->currencyMock + $this->currencyMock, + $this->priceHelperMock ); } @@ -46,8 +54,6 @@ class ValidationMessageTest extends \PHPUnit\Framework\TestCase { $minimumAmount = 20; $minimumAmountCurrency = '$20'; - $currencyCode = 'currency_code'; - $this->scopeConfigMock->expects($this->at(0)) ->method('getValue') ->with('sales/minimum_order/description', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) @@ -58,27 +64,13 @@ class ValidationMessageTest extends \PHPUnit\Framework\TestCase ->with('sales/minimum_order/amount', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) ->willReturn($minimumAmount); - $storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, ['getCurrentCurrencyCode']); - $storeMock->expects($this->once())->method('getCurrentCurrencyCode')->willReturn($currencyCode); - $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($storeMock); + $this->priceHelperMock->expects($this->once()) + ->method('currency') + ->with($minimumAmount, true, false) + ->will($this->returnValue($minimumAmountCurrency)); - $currencyMock = $this->createMock(\Magento\Framework\Currency::class); - $this->currencyMock->expects($this->once()) - ->method('getCurrency') - ->with($currencyCode) - ->willReturn($currencyMock); - - $currencyMock->expects($this->once()) - ->method('toCurrency') - ->with($minimumAmount) - ->willReturn($minimumAmountCurrency); - - $this->assertEquals( - __('Minimum order amount is %1', $minimumAmountCurrency), - $this->model->getMessage() - ); + $this->assertEquals(__('Minimum order amount is %1', $minimumAmountCurrency), $this->model->getMessage()); } - public function testGetConfigMessage() { $configMessage = 'config_message'; diff --git a/app/code/Magento/Quote/etc/module.xml b/app/code/Magento/Quote/etc/module.xml index f682568e63d02156b619867b835c1388d24f1c93..6607dea5809b1236d42c9f4012cc637bdb42069b 100644 --- a/app/code/Magento/Quote/etc/module.xml +++ b/app/code/Magento/Quote/etc/module.xml @@ -6,6 +6,6 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Quote" setup_version="2.0.6"> + <module name="Magento_Quote" setup_version="2.0.7"> </module> </config> diff --git a/app/code/Magento/Review/Controller/Product/ListAction.php b/app/code/Magento/Review/Controller/Product/ListAction.php index dd8b272867c551aefd5690f51ae65c9a58dad7af..26344d125172a86fe32582230eee2033a0139d30 100644 --- a/app/code/Magento/Review/Controller/Product/ListAction.php +++ b/app/code/Magento/Review/Controller/Product/ListAction.php @@ -26,8 +26,8 @@ class ListAction extends ProductController $resultPage->getConfig()->setPageLayout($product->getPageLayout()); } $urlSafeSku = rawurlencode($product->getSku()); - $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); $resultPage->addPageLayoutHandles(['type' => $product->getTypeId()], null, false); + $resultPage->addPageLayoutHandles(['id' => $product->getId(), 'sku' => $urlSafeSku]); $resultPage->addUpdate($product->getCustomLayoutUpdate()); return $resultPage; } diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php index 63c266150384ae4703724df39ec20e64054a8e6b..c45a1982784e17696b595f0d15102f068fc01a00 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/Save.php @@ -191,14 +191,7 @@ class Save extends \Magento\Backend\App\Action } $transactionSave->save(); - if (isset($shippingResponse) && $shippingResponse->hasErrors()) { - $this->messageManager->addError( - __( - 'The invoice and the shipment have been created. ' . - 'The shipping label cannot be created now.' - ) - ); - } elseif (!empty($data['do_shipment'])) { + if (!empty($data['do_shipment'])) { $this->messageManager->addSuccess(__('You created the invoice and shipment.')); } else { $this->messageManager->addSuccess(__('The invoice has been created.')); diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index 69f4d19e4dd630a45242df8e507de199525602d5..8ea09e6fb3d42de9e8ffc9b6c2f061200d37e2a3 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -681,7 +681,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ */ public function getCustomerWishlist($cacheReload = false) { - if (!is_null($this->_wishlist) && !$cacheReload) { + if (($this->_wishlist !== null) && !$cacheReload) { return $this->_wishlist; } @@ -708,7 +708,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ */ public function getCustomerCart() { - if (!is_null($this->_cart)) { + if ($this->_cart !== null) { return $this->_cart; } @@ -736,7 +736,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ */ public function getCustomerCompareList() { - if (!is_null($this->_compareList)) { + if ($this->_compareList !== null) { return $this->_compareList; } $customerId = (int)$this->getSession()->getCustomerId(); @@ -807,7 +807,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ break; case 'cart': $cart = $this->getCustomerCart(); - if ($cart && is_null($item->getOptionByCode('additional_options'))) { + if ($cart && ($item->getOptionByCode('additional_options') === null)) { //options and info buy request $product = $this->_objectManager->create( \Magento\Catalog\Model\Product::class @@ -1727,7 +1727,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ } $data = $form->restoreData($data); foreach ($data as $key => $value) { - if (!is_null($value)) { + if ($value !== null) { unset($data[$key]); } } @@ -1851,12 +1851,12 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ switch ($addressType) { case \Magento\Quote\Model\Quote\Address::ADDRESS_TYPE_BILLING: - if (is_null($customer->getDefaultBilling())) { + if ($customer->getDefaultBilling() === null) { $customerAddress->setIsDefaultBilling(true); } break; case \Magento\Quote\Model\Quote\Address::ADDRESS_TYPE_SHIPPING: - if (is_null($customer->getDefaultShipping())) { + if ($customer->getDefaultShipping() === null) { $customerAddress->setIsDefaultShipping(true); } break; @@ -1948,7 +1948,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ protected function _validate() { $customerId = $this->getSession()->getCustomerId(); - if (is_null($customerId)) { + if ($customerId === null) { throw new \Magento\Framework\Exception\LocalizedException(__('Please select a customer')); } diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo.php b/app/code/Magento/Sales/Model/Order/Creditmemo.php index 64b903fe5b5c1eacca4eb2b0241f71102f523176..0d0e0d23496b75ba8781c500d36463c0aff59229 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo.php @@ -431,7 +431,7 @@ class Creditmemo extends AbstractModel implements EntityInterface, CreditmemoInt /** * If we not retrieve negative answer from payment yet */ - if (is_null($canVoid)) { + if ($canVoid === null) { $canVoid = $this->getOrder()->getPayment()->canVoid(); if ($canVoid === false) { $this->setCanVoidFlag(false); @@ -451,7 +451,7 @@ class Creditmemo extends AbstractModel implements EntityInterface, CreditmemoInt */ public static function getStates() { - if (is_null(static::$_states)) { + if (static::$_states === null) { static::$_states = [ self::STATE_OPEN => __('Pending'), self::STATE_REFUNDED => __('Refunded'), @@ -469,11 +469,11 @@ class Creditmemo extends AbstractModel implements EntityInterface, CreditmemoInt */ public function getStateName($stateId = null) { - if (is_null($stateId)) { + if ($stateId === null) { $stateId = $this->getState(); } - if (is_null(static::$_states)) { + if (static::$_states === null) { static::getStates(); } if (isset(static::$_states[$stateId])) { diff --git a/app/code/Magento/Sales/Model/Order/CustomerManagement.php b/app/code/Magento/Sales/Model/Order/CustomerManagement.php index bf54e65d0ce1058ccac59429ba6ad8021f113339..466f3ff8adddb61b8ff3fd129a54a49b5c9e9408 100644 --- a/app/code/Magento/Sales/Model/Order/CustomerManagement.php +++ b/app/code/Magento/Sales/Model/Order/CustomerManagement.php @@ -131,6 +131,7 @@ class CustomerManagement implements \Magento\Sales\Api\OrderCustomerManagementIn $customer = $this->customerFactory->create(['data' => $customerData]); $account = $this->accountManagement->createAccount($customer); $order->setCustomerId($account->getId()); + $order->setCustomerIsGuest(0); $this->orderRepository->save($order); return $account; diff --git a/app/code/Magento/Sales/Model/Order/Payment/Transaction.php b/app/code/Magento/Sales/Model/Order/Payment/Transaction.php index b9b7a142095d9de0750949dbfa68d5914c3a4d1b..7540ee1902b572e72fab886c4db2e9292126bdac 100644 --- a/app/code/Magento/Sales/Model/Order/Payment/Transaction.php +++ b/app/code/Magento/Sales/Model/Order/Payment/Transaction.php @@ -733,7 +733,7 @@ class Transaction extends AbstractModel implements TransactionInterface */ public function getOrderWebsiteId() { - if (is_null($this->_orderWebsiteId)) { + if ($this->_orderWebsiteId === null) { $this->_orderWebsiteId = (int)$this->getResource()->getOrderWebsiteId($this->getOrderId()); } return $this->_orderWebsiteId; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index 850e9cf08413bc795ee6759335c16963f63e63c7..1b80d08e68cda046cbdc887e76a45649f57d4408 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -804,7 +804,7 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject throw new \Magento\Framework\Exception\LocalizedException(__('We found an invalid renderer model.')); } - if (is_null($this->_renderers[$type]['renderer'])) { + if ($this->_renderers[$type]['renderer'] === null) { $this->_renderers[$type]['renderer'] = $this->_pdfItemsFactory->get($this->_renderers[$type]['model']); } @@ -953,7 +953,7 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject * feed int; x position (required) * font string; font style, optional: bold, italic, regular * font_file string; path to font file (optional for use your custom font) - * font_size int; font size (default 7) + * font_size int; font size (default 10) * align string; text align (also see feed parametr), optional left, right * height int;line spacing (default 10) * @@ -1005,24 +1005,8 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject foreach ($lines as $line) { $maxHeight = 0; foreach ($line as $column) { - $fontSize = empty($column['font_size']) ? 10 : $column['font_size']; - if (!empty($column['font_file'])) { - $font = \Zend_Pdf_Font::fontWithPath($column['font_file']); - $page->setFont($font, $fontSize); - } else { - $fontStyle = empty($column['font']) ? 'regular' : $column['font']; - switch ($fontStyle) { - case 'bold': - $font = $this->_setFontBold($page, $fontSize); - break; - case 'italic': - $font = $this->_setFontItalic($page, $fontSize); - break; - default: - $font = $this->_setFontRegular($page, $fontSize); - break; - } - } + $font = $this->setFont($page, $column); + $fontSize = $column['font_size']; if (!is_array($column['text'])) { $column['text'] = [$column['text']]; @@ -1033,6 +1017,8 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject foreach ($column['text'] as $part) { if ($this->y - $lineSpacing < 15) { $page = $this->newPage($pageSettings); + $font = $this->setFont($page, $column); + $fontSize = $column['font_size']; } $feed = $column['feed']; @@ -1066,4 +1052,42 @@ abstract class AbstractPdf extends \Magento\Framework\DataObject return $page; } + + /** + * Set page font. + * + * column array format + * font string; font style, optional: bold, italic, regular + * font_file string; path to font file (optional for use your custom font) + * font_size int; font size (default 10) + * + * @param \Zend_Pdf_Page $page + * @param array $column + * @return \Zend_Pdf_Resource_Font + * @throws \Zend_Pdf_Exception + */ + private function setFont($page, &$column) + { + $fontSize = empty($column['font_size']) ? 10 : $column['font_size']; + $column['font_size'] = $fontSize; + if (!empty($column['font_file'])) { + $font = \Zend_Pdf_Font::fontWithPath($column['font_file']); + $page->setFont($font, $fontSize); + } else { + $fontStyle = empty($column['font']) ? 'regular' : $column['font']; + switch ($fontStyle) { + case 'bold': + $font = $this->_setFontBold($page, $fontSize); + break; + case 'italic': + $font = $this->_setFontItalic($page, $fontSize); + break; + default: + $font = $this->_setFontRegular($page, $fontSize); + break; + } + } + + return $font; + } } diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Invoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Invoice.php index f294128a72f9f2df99360c201f0d1ce8f5fe8e81..ba99ed083e952dbc9714a4cf2b3059297523cffd 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Invoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Invoice.php @@ -96,7 +96,7 @@ class Invoice extends AbstractPdf $lines[0][] = ['text' => __('Qty'), 'feed' => 435, 'align' => 'right']; - $lines[0][] = ['text' => __('Price'), 'feed' => 360, 'align' => 'right']; + $lines[0][] = ['text' => __('Price'), 'feed' => 375, 'align' => 'right']; $lines[0][] = ['text' => __('Tax'), 'feed' => 495, 'align' => 'right']; diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php index bb6078e425900a94adc2cfb358cbf8a6eac439eb..7d62e839ad92463472421faf721206a8d341b234 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php @@ -81,8 +81,8 @@ class DefaultInvoice extends \Magento\Sales\Model\Order\Pdf\Items\AbstractItems // draw item Prices $i = 0; $prices = $this->getItemPricesForDisplay(); - $feedPrice = 395; - $feedSubtotal = $feedPrice + 170; + $feedPrice = 375; + $feedSubtotal = $feedPrice + 190; foreach ($prices as $priceData) { if (isset($priceData['label'])) { // draw Price label diff --git a/app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers/Collection.php index 7f0aaff02d1040403b4cf384fc75b46694f54860..fa4fccb1b17e768ed0906d4f04345bc50e4d78c9 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers/Collection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Report/Bestsellers/Collection.php @@ -255,8 +255,8 @@ class Collection extends \Magento\Sales\Model\ResourceModel\Report\Collection\Ab $selectUnions = []; // apply date boundaries (before calling $this->_applyDateRangeFilter()) - $periodFrom = !is_null($this->_from) ? new \DateTime($this->_from) : null; - $periodTo = !is_null($this->_to) ? new \DateTime($this->_to) : null; + $periodFrom = ($this->_from !== null) ? new \DateTime($this->_from) : null; + $periodTo = ($this->_to !== null) ? new \DateTime($this->_to) : null; if ('year' == $this->_period) { if ($periodFrom) { // not the first day of the year diff --git a/app/code/Magento/Sales/i18n/en_US.csv b/app/code/Magento/Sales/i18n/en_US.csv index 65682843002255d8b6a1078280896fba632dc644..a32f3cacce7564a8cc3bf40db65e84136757951a 100644 --- a/app/code/Magento/Sales/i18n/en_US.csv +++ b/app/code/Magento/Sales/i18n/en_US.csv @@ -233,7 +233,6 @@ Sales,Sales "You can't create an invoice without products.","You can't create an invoice without products." "New Invoice","New Invoice" "We can't save the invoice right now.","We can't save the invoice right now." -"The invoice and the shipment have been created. The shipping label cannot be created now.","The invoice and the shipment have been created. The shipping label cannot be created now." "You created the invoice and shipment.","You created the invoice and shipment." "The invoice has been created.","The invoice has been created." "We can't send the invoice email right now.","We can't send the invoice email right now." diff --git a/app/code/Magento/Sales/view/frontend/templates/email/creditmemo/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/creditmemo/items.phtml index 297e31d6d2c981151b1b7f6c5edbd109782d763e..8cef5d57664a9c139a1f3fd423f6d69d2523734c 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/creditmemo/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/creditmemo/items.phtml @@ -25,14 +25,11 @@ </tr> </thead> <?php foreach ($_creditmemo->getAllItems() as $_item): ?> - <?php - if ($_item->getOrderItem()->getParentItem()) { - continue; - } - ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> <tfoot class="order-totals"> <?= $block->getChildHtml('creditmemo_totals') ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml index 10e07d1365470ed9a2de75a4c9e220d35fd27784..4c377dea47da2768d1443172dd8ceb1f8063ddcc 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/invoice/items.phtml @@ -25,14 +25,11 @@ </tr> </thead> <?php foreach ($_invoice->getAllItems() as $_item): ?> - <?php - if ($_item->getOrderItem()->getParentItem()) { - continue; - } - ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> <tfoot class="order-totals"> <?= $block->getChildHtml('invoice_totals') ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/items.phtml index 358264463d49a67f1c83b3ab6fb0215af1bca77c..37469582865dc8e3c6288d97cef14fc792e0c038 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/items.phtml @@ -25,14 +25,11 @@ </tr> </thead> <?php foreach ($_items as $_item): ?> - <?php - if ($_item->getParentItem()) { - continue; - } - ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getParentItem()) : ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> <tfoot class="order-totals"> <?= $block->getChildHtml('order_totals') ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/email/shipment/items.phtml b/app/code/Magento/Sales/view/frontend/templates/email/shipment/items.phtml index 2d2b7b2c2b26ee5afcdb0f49150e3f5584571db7..022511ae3cfd0304b93195144c319d93d99d1988 100644 --- a/app/code/Magento/Sales/view/frontend/templates/email/shipment/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/email/shipment/items.phtml @@ -22,14 +22,11 @@ </tr> </thead> <?php foreach ($_shipment->getAllItems() as $_item): ?> - <?php - if ($_item->getOrderItem()->getParentItem()) { - continue; - } - ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> </table> <?php endif; ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/creditmemo/items.phtml b/app/code/Magento/Sales/view/frontend/templates/order/creditmemo/items.phtml index a90300efaf5c96c7b7436ec316c1b1b59b553c3e..dc2cf2433ac8dd6cb1962ac4843769913f774a52 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/creditmemo/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/creditmemo/items.phtml @@ -40,14 +40,12 @@ </tr> </thead> <?php $_items = $_creditmemo->getAllItems(); ?> - <?php $_count = count($_items) ?> <?php foreach ($_items as $_item): ?> - <?php if ($_item->getOrderItem()->getParentItem()) { - continue; -} ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> <tfoot> <?= $block->getTotalsHtml($_creditmemo) ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/invoice/items.phtml b/app/code/Magento/Sales/view/frontend/templates/order/invoice/items.phtml index 980710569fa3b9ab61d0f2df74665625a2c298d1..0f3236ec25bc30b20ed8061923dda5b13bb09014 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/invoice/items.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/invoice/items.phtml @@ -37,14 +37,12 @@ </tr> </thead> <?php $_items = $_invoice->getAllItems(); ?> - <?php $_count = count($_items) ?> <?php foreach ($_items as $_item): ?> - <?php if ($_item->getOrderItem()->getParentItem()) { - continue; -} ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> <tfoot> <?= $block->getInvoiceTotalsHtml($_invoice) ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/print/creditmemo.phtml b/app/code/Magento/Sales/view/frontend/templates/order/print/creditmemo.phtml index d68b4cdb62fccfac957deddf2f64d2b0404a62ec..567dfc20f2de270ec5d03a84d0fc3b9d6ea6c7b7 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/print/creditmemo.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/print/creditmemo.phtml @@ -34,14 +34,12 @@ </tr> </thead> <?php $_items = $_creditmemo->getAllItems(); ?> - <?php $_count = count($_items); ?> <?php foreach ($_items as $_item): ?> - <?php if ($_item->getOrderItem()->getParentItem()) { - continue; -} ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> <tfoot> <?= $block->getTotalsHtml($_creditmemo) ?> diff --git a/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml b/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml index 5aa1cab686e2c0221879e85aaf8c76536c08a5e0..6fe6da9e7520962da97e7a67b9b1f36c8113d204 100644 --- a/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml +++ b/app/code/Magento/Sales/view/frontend/templates/order/print/invoice.phtml @@ -32,14 +32,12 @@ </tr> </thead> <?php $_items = $_invoice->getItemsCollection(); ?> - <?php $_count = $_items->count(); ?> <?php foreach ($_items as $_item): ?> - <?php if ($_item->getOrderItem()->getParentItem()) { - continue; -} ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getOrderItem()->getParentItem()): ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> <tfoot> <?= $block->getInvoiceTotalsHtml($_invoice) ?> diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js b/app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js index 5b047005962727ddff7bd8a49c68d3853f2eb8e2..6df769a90894e9916549daed4967d061730db622 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/view/summary/discount.js @@ -44,6 +44,25 @@ define([ return this.totals()['coupon_label']; }, + /** + * Get discount title + * + * @returns {null|String} + */ + getTitle: function () { + var discountSegments; + + if (!this.totals()) { + return null; + } + + discountSegments = this.totals()['total_segments'].filter(function (segment) { + return segment.code === 'discount'; + }); + + return discountSegments.length ? discountSegments[0].title : null; + }, + /** * @return {Number} */ diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/cart/totals/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/cart/totals/discount.html index 4b70b4b110c976a3b59ba143af798848c4b426fb..8fbb4a6ce74ae0e6ba13e62654d2ef8be20e0779 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/cart/totals/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/cart/totals/discount.html @@ -7,7 +7,7 @@ <!-- ko if: isDisplayed() --> <tr class="totals"> <th colspan="1" style="" class="mark" scope="row"> - <span class="title" data-bind="text: title"></span> + <span class="title" data-bind="text: getTitle()"></span> <span class="discount coupon" data-bind="text: getCouponLabel()"></span> </th> <td class="amount" data-bind="attr: {'data-th': title}"> diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html index 7246460382fa75e371257ee8d0646cf007d17f82..d622b5ea5762d7fa796f3a8999c0e16b844c461d 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html @@ -27,7 +27,7 @@ id="discount-code" name="discount_code" data-validate="{'required-entry':true}" - data-bind="value: couponCode, attr:{placeholder: $t('Enter discount code')} " /> + data-bind="value: couponCode, attr:{disabled:isApplied() , placeholder: $t('Enter discount code')} " /> </div> </div> </div> diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/summary/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/summary/discount.html index 17a9559fa01f2565f27cadf957a4d9307dffb81a..017e358c7e41992b27516bd38e7f9b8da7894ee5 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/summary/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/summary/discount.html @@ -7,7 +7,7 @@ <!-- ko if: isDisplayed() --> <tr class="totals discount"> <th class="mark" scope="row"> - <span class="title" data-bind="text: title"></span> + <span class="title" data-bind="text: getTitle()"></span> <span class="discount coupon" data-bind="text: getCouponCode()"></span> </th> <td class="amount"> diff --git a/app/code/Magento/Security/Model/AdminSessionsManager.php b/app/code/Magento/Security/Model/AdminSessionsManager.php index 4ebdcc58240a1ee16216ba0c28fcd3a8c104357e..af690f1899e7beb485740509cd2d8015cf2711f8 100644 --- a/app/code/Magento/Security/Model/AdminSessionsManager.php +++ b/app/code/Magento/Security/Model/AdminSessionsManager.php @@ -66,6 +66,14 @@ class AdminSessionsManager */ private $remoteAddress; + /** + * Max lifetime for session prolong to be valid (sec) + * + * Means that after session was prolonged + * all other prolongs will be ignored within this period + */ + private $maxIntervalBetweenConsecutiveProlongs = 60; + /** * @param ConfigInterface $securityConfig * @param \Magento\Backend\Model\Auth\Session $authSession @@ -124,11 +132,16 @@ class AdminSessionsManager */ public function processProlong() { - $this->getCurrentSession()->setData( - 'updated_at', - $this->authSession->getUpdatedAt() - ); - $this->getCurrentSession()->save(); + if ($this->lastProlongIsOldEnough()) { + $this->getCurrentSession()->setData( + 'updated_at', + date( + \Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT, + $this->authSession->getUpdatedAt() + ) + ); + $this->getCurrentSession()->save(); + } return $this; } @@ -298,4 +311,45 @@ class AdminSessionsManager { return $this->adminSessionInfoCollectionFactory->create(); } + + /** + * Calculates diff between now and last session updated_at + * and decides whether new prolong must be triggered or not + * + * This is done to limit amount of session prolongs and updates to database + * within some period of time - X + * X - is calculated in getIntervalBetweenConsecutiveProlongs() + * + * @see getIntervalBetweenConsecutiveProlongs() + * @return bool + */ + private function lastProlongIsOldEnough() + { + $lastProlongTimestamp = strtotime($this->getCurrentSession()->getUpdatedAt()); + $nowTimestamp = $this->authSession->getUpdatedAt(); + + $diff = $nowTimestamp - $lastProlongTimestamp; + + return (float) $diff > $this->getIntervalBetweenConsecutiveProlongs(); + } + + /** + * Calculates lifetime for session prolong to be valid + * + * Calculation is based on admin session lifetime + * Calculated result is in seconds and is in the interval + * between 1 (including) and MAX_INTERVAL_BETWEEN_CONSECUTIVE_PROLONGS (including) + * + * @return float + */ + private function getIntervalBetweenConsecutiveProlongs() + { + return (float) max( + 1, + min( + 4 * log((float)$this->securityConfig->getAdminSessionLifetime()), + $this->maxIntervalBetweenConsecutiveProlongs + ) + ); + } } diff --git a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php index d81264f661762ca2a29cad90d203684b7ce01339..ddfeaa59ac224f72bab2cf3aa12537c693e8ac9a 100644 --- a/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/AdminSessionsManagerTest.php @@ -99,7 +99,8 @@ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase 'setIsOtherSessionsTerminated', 'save', 'getUserId', - 'getSessionId' + 'getSessionId', + 'getUpdatedAt' ]); $this->securityConfigMock = $this->getMockBuilder(\Magento\Security\Model\ConfigInterface::class) @@ -216,7 +217,8 @@ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase public function testProcessProlong() { $sessionId = 50; - $updatedAt = '2015-12-31 23:59:59'; + $lastUpdatedAt = '2015-12-31 23:59:59'; + $newUpdatedAt = '2016-01-01 00:00:30'; $this->adminSessionInfoFactoryMock->expects($this->any()) ->method('create') @@ -230,13 +232,21 @@ class AdminSessionsManagerTest extends \PHPUnit\Framework\TestCase ->method('load') ->willReturnSelf(); - $this->authSessionMock->expects($this->once()) + $this->currentSessionMock->expects($this->once()) + ->method('getUpdatedAt') + ->willReturn($lastUpdatedAt); + + $this->authSessionMock->expects($this->exactly(2)) ->method('getUpdatedAt') - ->willReturn($updatedAt); + ->willReturn(strtotime($newUpdatedAt)); + + $this->securityConfigMock->expects($this->once()) + ->method('getAdminSessionLifetime') + ->willReturn(100); $this->currentSessionMock->expects($this->once()) ->method('setData') - ->with('updated_at', $updatedAt) + ->with('updated_at', $newUpdatedAt) ->willReturnSelf(); $this->currentSessionMock->expects($this->once()) diff --git a/app/code/Magento/Shipping/Helper/Data.php b/app/code/Magento/Shipping/Helper/Data.php index 78e23cb4aeac266b753c44a3838dacd2d017a168..dd0933b5a340e80a2727a208c66c32304cf09ead 100644 --- a/app/code/Magento/Shipping/Helper/Data.php +++ b/app/code/Magento/Shipping/Helper/Data.php @@ -11,6 +11,10 @@ */ namespace Magento\Shipping\Helper; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\UrlInterface; +use Magento\Store\Model\StoreManagerInterface; + class Data extends \Magento\Framework\App\Helper\AbstractHelper { /** @@ -21,19 +25,28 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper protected $_allowedHashKeys = ['ship_id', 'order_id', 'track_id']; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $_storeManager; + /** + * @var UrlInterface|null + */ + private $url; + /** * @param \Magento\Framework\App\Helper\Context $context - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param StoreManagerInterface $storeManager + * @param UrlInterface|null $url */ public function __construct( \Magento\Framework\App\Helper\Context $context, - \Magento\Store\Model\StoreManagerInterface $storeManager + StoreManagerInterface $storeManager, + UrlInterface $url = null ) { $this->_storeManager = $storeManager; + $this->url = $url ?: ObjectManager::getInstance()->get(UrlInterface::class); + parent::__construct($context); } @@ -64,12 +77,13 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper { $urlPart = "{$key}:{$model->{$method}()}:{$model->getProtectCode()}"; $params = [ + '_scope' => $model->getStoreId(), + '_nosid' => true, '_direct' => 'shipping/tracking/popup', '_query' => ['hash' => $this->urlEncoder->encode($urlPart)] ]; - $storeModel = $this->_storeManager->getStore($model->getStoreId()); - return $storeModel->getUrl('', $params); + return $this->url->getUrl('', $params); } /** diff --git a/app/code/Magento/Shipping/etc/adminhtml/di.xml b/app/code/Magento/Shipping/etc/adminhtml/di.xml index 54d5d9664e66f7ec06032e8c847f29336138adf9..36bd1ae9d35055444b9196ad2a6a788a04f87193 100644 --- a/app/code/Magento/Shipping/etc/adminhtml/di.xml +++ b/app/code/Magento/Shipping/etc/adminhtml/di.xml @@ -7,4 +7,11 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Shipping\Model\Shipping" type="Magento\Shipping\Model\Shipping\Labels" /> + + <type name="Magento\Shipping\Helper\Data"> + <arguments> + <!-- Use frontend URL model--> + <argument name="url" xsi:type="object">Magento\Framework\Url</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Shipping/view/frontend/templates/items.phtml b/app/code/Magento/Shipping/view/frontend/templates/items.phtml index 1f4958d1775fb3140c56c65373ce127dafa5c673..ebc6163a7bd06dfe4427444fc66f46bed46cf40b 100644 --- a/app/code/Magento/Shipping/view/frontend/templates/items.phtml +++ b/app/code/Magento/Shipping/view/frontend/templates/items.phtml @@ -66,14 +66,12 @@ </tr> </thead> <?php $_items = $_shipment->getAllItems(); ?> - <?php $_count = count($_items) ?> <?php foreach ($_items as $_item): ?> - <?php if ($_item->getOrderItem()->getParentItem()) { - continue; -} ?> - <tbody> - <?= $block->getItemHtml($_item) ?> - </tbody> + <?php if (!$_item->getOrderItem()->getParentItem()) : ?> + <tbody> + <?= $block->getItemHtml($_item) ?> + </tbody> + <?php endif; ?> <?php endforeach; ?> </table> </div> diff --git a/app/code/Magento/Sitemap/Model/ResourceModel/Cms/Page.php b/app/code/Magento/Sitemap/Model/ResourceModel/Cms/Page.php index d050ea84ecccb8ac25aa246ef9f1f5c53042cbcc..01addd0c19666fb0e8b80346167108bd0d4d96b5 100644 --- a/app/code/Magento/Sitemap/Model/ResourceModel/Cms/Page.php +++ b/app/code/Magento/Sitemap/Model/ResourceModel/Cms/Page.php @@ -6,12 +6,15 @@ namespace Magento\Sitemap\Model\ResourceModel\Cms; use Magento\Cms\Api\Data\PageInterface; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Framework\Model\ResourceModel\Db\Context; -use Magento\Framework\Model\AbstractModel; +use Magento\Cms\Api\GetUtilityPageIdentifiersInterface; use Magento\Cms\Model\Page as CmsPage; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\EntityManager; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\ResourceModel\Db\Context; /** * Sitemap cms page collection model @@ -19,7 +22,7 @@ use Magento\Framework\EntityManager\EntityManager; * @api * @since 100.0.2 */ -class Page extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +class Page extends AbstractDb { /** * @var MetadataPool @@ -34,19 +37,29 @@ class Page extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb protected $entityManager; /** - * @param Context $context - * @param MetadataPool $metadataPool - * @param EntityManager $entityManager - * @param string $connectionName + * @var GetUtilityPageIdentifiersInterface + * @since 100.2.0 + */ + private $getUtilityPageIdentifiers; + + /** + * @param Context $context + * @param MetadataPool $metadataPool + * @param EntityManager $entityManager + * @param string $connectionName + * @param GetUtilityPageIdentifiersInterface $getUtilityPageIdentifiers */ public function __construct( Context $context, MetadataPool $metadataPool, EntityManager $entityManager, - $connectionName = null + $connectionName = null, + GetUtilityPageIdentifiersInterface $getUtilityPageIdentifiers = null ) { - $this->metadataPool = $metadataPool; - $this->entityManager = $entityManager; + $this->metadataPool = $metadataPool; + $this->entityManager = $entityManager; + $this->getUtilityPageIdentifiers = $getUtilityPageIdentifiers ?: + ObjectManager::getInstance()->get(GetUtilityPageIdentifiersInterface::class); parent::__construct($context, $connectionName); } @@ -90,8 +103,8 @@ class Page extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb )->where( 'main_table.is_active = 1' )->where( - 'main_table.identifier != ?', - \Magento\Cms\Model\Page::NOROUTE_PAGE_ID + 'main_table.identifier NOT IN (?)', + $this->getUtilityPageIdentifiers->execute() )->where( 'store_table.store_id IN(?)', [0, $storeId] diff --git a/app/code/Magento/Sitemap/etc/module.xml b/app/code/Magento/Sitemap/etc/module.xml index 0edfcf84f644fb2e818fecd00c9cfa1a77bf515c..0cfe3d551d162b13f916032687f7ff306962adcc 100644 --- a/app/code/Magento/Sitemap/etc/module.xml +++ b/app/code/Magento/Sitemap/etc/module.xml @@ -9,6 +9,7 @@ <module name="Magento_Sitemap" setup_version="2.0.0"> <sequence> <module name="Magento_Robots"/> + <module name="Magento_Cms"/> <module name="Magento_Catalog"/> </sequence> </module> diff --git a/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/Visual.php b/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/Visual.php index 306f01af17e71f93a8487e5b5e1ed702a261a6aa..66ecd06c646e7980be68eb94de32eab394aac435 100644 --- a/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/Visual.php +++ b/app/code/Magento/Swatches/Block/Adminhtml/Attribute/Edit/Options/Visual.php @@ -84,15 +84,15 @@ class Visual extends AbstractSwatch * Parse swatch labels for template * * @codeCoverageIgnore - * @param null $swatchStoreValue - * @return string + * @param null|array $swatchStoreValue + * @return null|array */ protected function reformatSwatchLabels($swatchStoreValue = null) { if ($swatchStoreValue === null) { return; } - $newSwatch = ''; + $newSwatch = []; foreach ($swatchStoreValue as $key => $value) { if ($value[0] == '#') { $newSwatch[$key] = 'background: '.$value; diff --git a/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php b/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php index 599406f4552816ac0a3e5c2eb074a37156a3940f..d3904f058dc2db8bd4001c7b6eb8b4b9f47523c1 100644 --- a/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php +++ b/app/code/Magento/Swatches/Model/Plugin/EavAttribute.php @@ -432,7 +432,7 @@ class EavAttribute $options = $attribute->getData('optiontext'); } if ($options && !$this->isOptionsValid($options, $attribute)) { - throw new InputException(__('Admin is a required field in the each row')); + throw new InputException(__('Admin is a required field in each row')); } return true; } diff --git a/app/code/Magento/Swatches/Test/Unit/Model/Plugin/EavAttributeTest.php b/app/code/Magento/Swatches/Test/Unit/Model/Plugin/EavAttributeTest.php index 258347887ff08ac7ada538e18aea89234cd958c8..31a45ddb2847cd72c1f61fe94183fbec979ad2c4 100644 --- a/app/code/Magento/Swatches/Test/Unit/Model/Plugin/EavAttributeTest.php +++ b/app/code/Magento/Swatches/Test/Unit/Model/Plugin/EavAttributeTest.php @@ -191,7 +191,7 @@ class EavAttributeTest extends \PHPUnit\Framework\TestCase /** * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage Admin is a required field in the each row + * @expectedExceptionMessage Admin is a required field in each row */ public function testBeforeSaveWithFailedValidation() { diff --git a/app/code/Magento/Swatches/i18n/en_US.csv b/app/code/Magento/Swatches/i18n/en_US.csv index 3ea4977a75348e621707de5952093eff09446d54..00874d7611169530b54227ae7d2cd215a7458d4b 100644 --- a/app/code/Magento/Swatches/i18n/en_US.csv +++ b/app/code/Magento/Swatches/i18n/en_US.csv @@ -1,4 +1,4 @@ -"Admin is a required field in the each row","Admin is a required field in the each row" +"Admin is a required field in each row","Admin is a required field in each row" "Update Product Preview Image","Update Product Preview Image" "Filtering by this attribute will update the product image on catalog page","Filtering by this attribute will update the product image on catalog page" "Use Product Image for Swatch if Possible","Use Product Image for Swatch if Possible" diff --git a/app/code/Magento/Swatches/view/adminhtml/ui_component/product_attribute_add_form.xml b/app/code/Magento/Swatches/view/adminhtml/ui_component/product_attribute_add_form.xml index 4152c06fa3ddc5d153f93da6b9c2c6c278b8152c..2fdf5f3cd0ea92b56d7cf4413c59d72fc9b29425 100644 --- a/app/code/Magento/Swatches/view/adminhtml/ui_component/product_attribute_add_form.xml +++ b/app/code/Magento/Swatches/view/adminhtml/ui_component/product_attribute_add_form.xml @@ -87,7 +87,6 @@ <additionalClasses> <class name="admin__field-wide">true</class> </additionalClasses> - <dataScope>text_swatch</dataScope> <componentType>dynamicRows</componentType> </settings> <container name="record" component="Magento_Ui/js/dynamic-rows/record"> @@ -96,8 +95,6 @@ <item name="isTemplate" xsi:type="boolean">true</item> <item name="is_collection" xsi:type="boolean">true</item> <item name="componentType" xsi:type="string">container</item> - <item name="positionProvider" xsi:type="string">text_swatch.position</item> - <item name="dataScope" xsi:type="string"/> </item> </argument> <field name="defaulttext" component="Magento_Catalog/js/form/element/checkbox" sortOrder="0" formElement="checkbox"> @@ -183,15 +180,11 @@ </item> </argument> <settings> - <additionalClasses> - <class name="_hidden">true</class> - </additionalClasses> <dataType>text</dataType> <visible>false</visible> - <dataScope>position</dataScope> </settings> </field> - <actionDelete name="action_delete" component="Magento_Catalog/js/form/element/action-delete" template="Magento_Catalog/form/element/action-delete"> + <actionDelete name="action_delete"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="fit" xsi:type="boolean">true</item> @@ -227,7 +220,6 @@ <class name="swatch-visual-options-container">true</class> <class name="admin__field-wide">true</class> </additionalClasses> - <dataScope>visual_swatch</dataScope> <componentType>dynamicRows</componentType> </settings> <container name="record" component="Magento_Ui/js/dynamic-rows/record"> @@ -236,8 +228,6 @@ <item name="isTemplate" xsi:type="boolean">true</item> <item name="is_collection" xsi:type="boolean">true</item> <item name="componentType" xsi:type="string">container</item> - <item name="positionProvider" xsi:type="string">text_swatch.position</item> - <item name="dataScope" xsi:type="string"/> </item> </argument> <field name="defaultvisual" component="Magento_Catalog/js/form/element/checkbox" sortOrder="0" formElement="checkbox"> @@ -276,7 +266,6 @@ <class name="swatches-visual-col">true</class> </additionalClasses> <label translate="true">Swatch</label> - <dataScope>swatchvisual</dataScope> </settings> </field> <field name="optionvisual_default_store_view" component="Magento_Catalog/js/form/element/input" template="Magento_Catalog/form/element/input" formElement="input"> @@ -290,7 +279,6 @@ <settings> <dataType>text</dataType> <label translate="true">Default Store View</label> - <dataScope>optionvisual_default_store_view</dataScope> </settings> </field> <field name="optionvisual_admin" component="Magento_Catalog/js/form/element/input" template="Magento_Catalog/form/element/input" formElement="input"> @@ -304,7 +292,6 @@ <settings> <dataType>text</dataType> <label translate="true">Admin</label> - <dataScope>optionvisual_admin</dataScope> </settings> </field> <field name="position" component="Magento_Catalog/js/form/element/input" formElement="input"> @@ -315,15 +302,10 @@ </item> </argument> <settings> - <additionalClasses> - <class name="_hidden">true</class> - </additionalClasses> - <dataType>text</dataType> - <visible>false</visible> - <dataScope>position</dataScope> + <visible>false</visible> </settings> </field> - <actionDelete name="action_delete" component="Magento_Catalog/js/form/element/action-delete" template="Magento_Catalog/form/element/action-delete"> + <actionDelete name="action_delete"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="fit" xsi:type="boolean">true</item> diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/form/element/swatch-visual.js b/app/code/Magento/Swatches/view/adminhtml/web/js/form/element/swatch-visual.js index e63c9a2138a3627671343e11a02908b1d4799653..2fbce5aefbdebe34e025a71e4062cd2a22361140 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/form/element/swatch-visual.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/form/element/swatch-visual.js @@ -305,18 +305,30 @@ define([ */ initialize: function () { this._super() - .initOldCode(); + .initOldCode() + .on('value', this.onChangeColor.bind(this)); return this; }, + /** + * Handler function that execute when color changes. + * + * @param {String} data - color + */ + onChangeColor: function (data) { + if (!data) { + jQuery('.' + this.elementName).parent().removeClass('unavailable'); + } + }, + /** * Initialize wrapped former implementation. * * @returns {Object} Chainable. */ initOldCode: function () { - jQuery.async('.' + this.elementName, function (elem) { + jQuery.async('.' + this.elementName, this.name, function (elem) { oldCode(this.value(), elem.parentElement, this.uploadUrl, this.elementName); }.bind(this)); @@ -336,9 +348,15 @@ define([ this.elementName = this.prefixElementName + recordId; this.inputName = prefixName + '[' + this.elementName + ']'; - this.dataScope = 'data.' + this.prefixName + '.' + this.elementName; + this.exportDataLink = 'data.' + this.prefixName + '.' + this.elementName; + this.exports.value = this.provider + ':' + this.exportDataLink; + }, + + /** @inheritdoc */ + destroy: function () { + this._super(); - this.links.value = this.provider + ':' + this.dataScope; + this.source.remove(this.exportDataLink); }, /** diff --git a/app/code/Magento/Swatches/view/adminhtml/web/template/swatch-visual.html b/app/code/Magento/Swatches/view/adminhtml/web/template/swatch-visual.html index 271cea918b7be2087d49e16161bb08e2f5a6c04f..8296e944a8ed3f7c2311c2eb75e4593545d0bc66 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/template/swatch-visual.html +++ b/app/code/Magento/Swatches/view/adminhtml/web/template/swatch-visual.html @@ -11,7 +11,7 @@ }, value: value "/> -<div data-bind="attr: {class: 'swatch_window ' + elementName}"></div> +<div attr="class: 'swatch_window ' + elementName" ko-style="backgroundColor: $data.value"></div> <div class="swatch_sub-menu_container"> <div class="swatch_row position-relative"> <div class="swatch_row_name colorpicker_handler"> diff --git a/app/code/Magento/Tax/Model/Calculation/Rate.php b/app/code/Magento/Tax/Model/Calculation/Rate.php index f639d6680a7fc039b95e3617c63f3664b1134e1d..9e44cd113ddeaa7d4b0ff1704c70edcb12a76df6 100644 --- a/app/code/Magento/Tax/Model/Calculation/Rate.php +++ b/app/code/Magento/Tax/Model/Calculation/Rate.php @@ -227,7 +227,7 @@ class Rate extends \Magento\Framework\Model\AbstractExtensibleModel implements T */ public function saveTitles($titles = null) { - if (is_null($titles)) { + if ($titles === null) { $titles = $this->getTitle(); } @@ -256,7 +256,7 @@ class Rate extends \Magento\Framework\Model\AbstractExtensibleModel implements T */ public function getTitleModel() { - if (is_null($this->_titleModel)) { + if ($this->_titleModel === null) { $this->_titleModel = $this->_titleFactory->create(); } return $this->_titleModel; @@ -270,7 +270,7 @@ class Rate extends \Magento\Framework\Model\AbstractExtensibleModel implements T if ($this->getData(self::KEY_TITLES)) { return $this->getData(self::KEY_TITLES); } - if (is_null($this->_titles)) { + if ($this->_titles === null) { $this->_titles = $this->getTitleModel()->getCollection()->loadByRateId($this->getId())->getItems(); } return $this->_titles; diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php index 1fcaf0e413cd5e6f7ba077422f9a59e998956f27..7752911ceb22f59bb0523623cbe20a913d3041db 100644 --- a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php +++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php @@ -771,7 +771,7 @@ class CommonTaxCollector extends AbstractTotal $previouslyAppliedTaxes[$row['id']] = $row; } - if (!is_null($row['percent'])) { + if ($row['percent'] !== null) { $row['percent'] = $row['percent'] ? $row['percent'] : 1; $rate = $rate ? $rate : 1; diff --git a/app/code/Magento/Theme/Block/Html/Topmenu.php b/app/code/Magento/Theme/Block/Html/Topmenu.php index 7747576988077c0311a287232cd36d1c46d9d827..ed3445e1173314a0634212dd0c998b3bf3aa81fd 100644 --- a/app/code/Magento/Theme/Block/Html/Topmenu.php +++ b/app/code/Magento/Theme/Block/Html/Topmenu.php @@ -235,7 +235,13 @@ class Topmenu extends Template implements IdentityInterface if ($childLevel == 0 && $outermostClass) { $outermostClassCode = ' class="' . $outermostClass . '" '; - $child->setClass($outermostClass); + $currentClass = $child->getClass(); + + if (empty($currentClass)) { + $child->setClass($outermostClass); + } else { + $child->setClass($currentClass . ' ' . $outermostClass); + } } if (count($colBrakes) && $colBrakes[$counter]['colbrake']) { diff --git a/app/code/Magento/Theme/etc/config.xml b/app/code/Magento/Theme/etc/config.xml index 925b906af952335c193342c0da8f1a3edc851956..8b740bc8d09aecb0f2fe226923a0dc35730a4bf7 100644 --- a/app/code/Magento/Theme/etc/config.xml +++ b/app/code/Magento/Theme/etc/config.xml @@ -45,7 +45,7 @@ Disallow: /*SID= <welcome>Default welcome msg!</welcome> </header> <footer translate="copyright"> - <copyright>Copyright © 2013-2017 Magento, Inc. All rights reserved.</copyright> + <copyright>Copyright © 2013-2018 Magento, Inc. All rights reserved.</copyright> </footer> </design> <theme> diff --git a/app/code/Magento/Theme/i18n/en_US.csv b/app/code/Magento/Theme/i18n/en_US.csv index 16cc5596b3a9302df61584f23c1b72a80732604f..daa5c27e75fccec0a767a125ccfaf74dbac6e58a 100644 --- a/app/code/Magento/Theme/i18n/en_US.csv +++ b/app/code/Magento/Theme/i18n/en_US.csv @@ -142,7 +142,7 @@ Empty,Empty "1 column","1 column" Configuration,Configuration "Default welcome msg!","Default welcome msg!" -"Copyright © 2013-2017 Magento, Inc. All rights reserved.","Copyright © 2013-2017 Magento, Inc. All rights reserved." +"Copyright © 2013-2018 Magento, Inc. All rights reserved.","Copyright © 2013-2018 Magento, Inc. All rights reserved." "Design Config Grid","Design Config Grid" "Rebuild design config grid index","Rebuild design config grid index" "Admin empty","Admin empty" diff --git a/app/code/Magento/Theme/view/frontend/templates/html/header.phtml b/app/code/Magento/Theme/view/frontend/templates/html/header.phtml index cc3ea276e2230c3daa639ca0e0fecf49b4bdc13d..58548a0ba268a1916fe503ae4e3b6da1e1871590 100644 --- a/app/code/Magento/Theme/view/frontend/templates/html/header.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/html/header.phtml @@ -15,7 +15,7 @@ $welcomeMessage = $block->getWelcome(); case 'welcome': ?> <li class="greet welcome" data-bind="scope: 'customer'"> <!-- ko if: customer().fullname --> - <span data-bind="text: new String('<?= $block->escapeHtml(__('Welcome, %1!', '%1')) ?>').replace('%1', customer().firstname)"> + <span data-bind="text: new String('<?= $block->escapeHtml(__('Welcome, %1!', '%1')) ?>').replace('%1', customer().fullname)"> </span> <!-- /ko --> <!-- ko ifnot: customer().fullname --> diff --git a/app/code/Magento/Translation/Model/Js/DataProvider.php b/app/code/Magento/Translation/Model/Js/DataProvider.php index 6212647110941b3786d16f974dcc8f567bc4d37f..9037574e28a5b4731e244023d1d8efcedfad5042 100644 --- a/app/code/Magento/Translation/Model/Js/DataProvider.php +++ b/app/code/Magento/Translation/Model/Js/DataProvider.php @@ -46,7 +46,7 @@ class DataProvider implements DataProviderInterface /** * Basic translate renderer * - * @var \Magento\Framework\Phrase\Renderer\Translate + * @var \Magento\Framework\Phrase\RendererInterface */ protected $translate; @@ -54,7 +54,7 @@ class DataProvider implements DataProviderInterface * @param \Magento\Framework\App\State $appState * @param Config $config * @param \Magento\Framework\Filesystem\File\ReadFactory $fileReadFactory - * @param \Magento\Framework\Phrase\Renderer\Translate $translate + * @param \Magento\Framework\Phrase\RendererInterface $translate * @param \Magento\Framework\Component\ComponentRegistrar $componentRegistrar * @param \Magento\Framework\Component\DirSearch $dirSearch * @param \Magento\Framework\View\Design\Theme\ThemePackageList $themePackageList @@ -64,7 +64,7 @@ class DataProvider implements DataProviderInterface \Magento\Framework\App\State $appState, Config $config, \Magento\Framework\Filesystem\File\ReadFactory $fileReadFactory, - \Magento\Framework\Phrase\Renderer\Translate $translate, + \Magento\Framework\Phrase\RendererInterface $translate, \Magento\Framework\Component\ComponentRegistrar $componentRegistrar, \Magento\Framework\Component\DirSearch $dirSearch, \Magento\Framework\View\Design\Theme\ThemePackageList $themePackageList, diff --git a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php index d2ce1a58725a8928d28b6d91589de1f6469efc8b..6eb503a64cc3d8c3d22b8450c8453c686f953133 100644 --- a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php +++ b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php @@ -88,7 +88,7 @@ class Render extends AbstractAction } catch (\Exception $e) { $this->logger->critical($e); $result = [ - 'error' => _('UI component could not be rendered because of system exception'), + 'error' => __('UI component could not be rendered because of system exception'), 'errorcode' => $this->escaper->escapeHtml($e->getCode()) ]; /** @var \Magento\Framework\Controller\Result\Json $resultJson */ diff --git a/app/code/Magento/Ui/i18n/en_US.csv b/app/code/Magento/Ui/i18n/en_US.csv index 225d83387563be037b4615c232535d2480af5fd5..cff52a3fd6fedf836d9ed37863312b8d005f3125 100644 --- a/app/code/Magento/Ui/i18n/en_US.csv +++ b/app/code/Magento/Ui/i18n/en_US.csv @@ -192,7 +192,7 @@ CSV,CSV "Please enter a valid value from list","Please enter a valid value from list" "Please enter valid SKU key.","Please enter valid SKU key." "Please enter a valid number.","Please enter a valid number." -"Admin is a required field in the each row.","Admin is a required field in the each row." +"Admin is a required field in each row.","Admin is a required field in each row." "Please fix this field.","Please fix this field." "Please enter a valid date (ISO).","Please enter a valid date (ISO)." "Please enter only digits.","Please enter only digits." diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js index 01fa03d1b4b67cb71427240a54af4b4e560cb73c..94a05e5fcde33efe26b2817d21fecfea403c21c6 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js @@ -224,6 +224,14 @@ define([ return this; }, + /** @inheritdoc */ + destroy: function () { + if (this.dnd()) { + this.dnd().destroy(); + } + this._super(); + }, + /** * Calls 'initObservable' of parent * diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/html.js b/app/code/Magento/Ui/view/base/web/js/form/components/html.js index 82e51aff402872c5abffce21db6a94599c437ce4..466b6840e9b195301d1db28e072a9c8db877d7e8 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/components/html.js +++ b/app/code/Magento/Ui/view/base/web/js/form/components/html.js @@ -19,6 +19,7 @@ define([ showSpinner: false, loading: false, visible: true, + error: false, template: 'ui/content/content', additionalClasses: {} }, diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/region.js b/app/code/Magento/Ui/view/base/web/js/form/element/region.js index 1b2ce8889af10112ddcd654a875e73c4823576ea..0edb4c1966b54e7f81fe921177e0d26f198d3697 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/region.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/region.js @@ -35,6 +35,11 @@ define([ return; } option = options[value]; + + if (typeof option === 'undefined') { + return; + } + defaultPostCodeResolver.setUseDefaultPostCode(!option['is_zipcode_optional']); if (this.skipValidation) { diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index 01a15755414088e21a010e08b8b5e5212d8d1a46..0ffd65c47d4cf67639992a976a00b1b59352ebd7 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -84,8 +84,10 @@ define([ ], 'range-words': [ function (value, params) { - return utils.stripHtml(value).match(/\b\w+\b/g).length >= params[0] && - value.match(/bw+b/g).length < params[1]; + var match = utils.stripHtml(value).match(/\b\w+\b/g) || []; + + return match.length >= params[0] && + match.length <= params[1]; }, $.mage.__('Please enter between {0} and {1} words.') ], diff --git a/app/code/Magento/Ui/view/base/web/templates/content/content.html b/app/code/Magento/Ui/view/base/web/templates/content/content.html index 8cf47120865dcbc172acd113fc634f9cd4a17b4e..62e5959a02559be8b3ed975b193ce793f1ba93d6 100644 --- a/app/code/Magento/Ui/view/base/web/templates/content/content.html +++ b/app/code/Magento/Ui/view/base/web/templates/content/content.html @@ -17,3 +17,5 @@ <span repeat="8"/> </div> </div> + +<label class="admin__field-error" if="error" attr="for: uid" text="error"/> diff --git a/app/code/Magento/Vault/Setup/InstallSchema.php b/app/code/Magento/Vault/Setup/InstallSchema.php index 32e1d62754439f5324c474f848517d48fce84b10..dd65f7a8c9772883318ce2f91d559c06a68f94f2 100644 --- a/app/code/Magento/Vault/Setup/InstallSchema.php +++ b/app/code/Magento/Vault/Setup/InstallSchema.php @@ -90,13 +90,13 @@ class InstallSchema implements InstallSchemaInterface 'is_active', Table::TYPE_BOOLEAN, null, - ['nullable' => false, 'dafault' => true], + ['nullable' => false, 'default' => true], 'Is active flag' )->addColumn( 'is_visible', Table::TYPE_BOOLEAN, null, - ['nullable' => false, 'dafault' => true], + ['nullable' => false, 'default' => true], 'Is visible flag' )->addIndex( $setup->getIdxName( diff --git a/app/code/Magento/Vault/Setup/UpgradeSchema.php b/app/code/Magento/Vault/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000000000000000000000000000000..643bd1751f6689a7b41ba4caffea7c4f11318bab --- /dev/null +++ b/app/code/Magento/Vault/Setup/UpgradeSchema.php @@ -0,0 +1,51 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Vault\Setup; + +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Setup\UpgradeSchemaInterface; +use Magento\Framework\DB\Ddl\Table; + +/** + * Upgrade the Vault module DB scheme + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * @inheritdoc + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + if (version_compare($context->getVersion(), '2.0.3', '<')) { + $this->upgradeTokenTableDefaultValues($setup); + } + $setup->endSetup(); + } + + /** + * @param SchemaSetupInterface $setup + * @return void + */ + private function upgradeTokenTableDefaultValues(SchemaSetupInterface $setup) + { + $columns = ['is_active', 'is_visible']; + + foreach ($columns as $columnName) { + $setup->getConnection()->modifyColumn( + $setup->getTable(InstallSchema::PAYMENT_TOKEN_TABLE), + $columnName, + [ + 'type' => Table::TYPE_BOOLEAN, + 'nullable' => false, + 'default' => '1' + ] + ); + } + } +} diff --git a/app/code/Magento/Vault/etc/module.xml b/app/code/Magento/Vault/etc/module.xml index 1a7d1fe7d09fd17c7d6109c81f83b7ac83e1fbf2..253e7f13aaadc4171ae26e4a877c231d69bac9d3 100644 --- a/app/code/Magento/Vault/etc/module.xml +++ b/app/code/Magento/Vault/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Vault" setup_version="2.0.2"> + <module name="Magento_Vault" setup_version="2.0.3"> <sequence> <module name="Magento_Sales"/> <module name="Magento_Store"/> diff --git a/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Manager/Website.php b/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Manager/Website.php index c5990c8a80b4ed82964e1043c776b8e494958f59..2374e2e57a5432cb5b11cf1d08f41aaa4f07c94d 100644 --- a/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Manager/Website.php +++ b/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Manager/Website.php @@ -68,7 +68,7 @@ class Website if ($storeId = $this->locator->getStore()->getId()) { /** @var WebsiteInterface $website */ $website = $this->storeManager->getStore($storeId)->getWebsite(); - $websites[$website->getId()] = [ + $websites[] = [ 'value' => $website->getId(), 'label' => $this->formatLabel( $website->getName(), @@ -81,7 +81,7 @@ class Website if (!in_array($website->getId(), $product->getWebsiteIds())) { continue; } - $websites[$website->getId()] = [ + $websites[] = [ 'value' => $website->getId(), 'label' => $this->formatLabel( $website->getName(), diff --git a/app/code/Magento/Widget/Model/Widget/Instance.php b/app/code/Magento/Widget/Model/Widget/Instance.php index 07a995e3f7734100c252e5df742c4b1f6e9178e7..f21609cb1ef5c5add6c2c579dd13e99a6fb973fc 100644 --- a/app/code/Magento/Widget/Model/Widget/Instance.php +++ b/app/code/Magento/Widget/Model/Widget/Instance.php @@ -34,7 +34,12 @@ class Instance extends \Magento\Framework\Model\AbstractModel const PRODUCT_LAYOUT_HANDLE = 'catalog_product_view'; - const SINGLE_PRODUCT_LAYOUT_HANLDE = 'catalog_product_view_id_{{ID}}'; + /** + * @deprecated see self::SINGLE_PRODUCT_LAYOUT_HANDLE + */ + const SINGLE_PRODUCT_LAYOUT_HANLDE = self::SINGLE_PRODUCT_LAYOUT_HANDLE; + + const SINGLE_PRODUCT_LAYOUT_HANDLE = 'catalog_product_view_id_{{ID}}'; const PRODUCT_TYPE_LAYOUT_HANDLE = 'catalog_product_view_type_{{TYPE}}'; @@ -188,12 +193,12 @@ class Instance extends \Magento\Framework\Model\AbstractModel $this->_specificEntitiesLayoutHandles = [ 'anchor_categories' => self::SINGLE_CATEGORY_LAYOUT_HANDLE, 'notanchor_categories' => self::SINGLE_CATEGORY_LAYOUT_HANDLE, - 'all_products' => self::SINGLE_PRODUCT_LAYOUT_HANLDE, + 'all_products' => self::SINGLE_PRODUCT_LAYOUT_HANDLE, ]; foreach (array_keys($this->_productType->getTypes()) as $typeId) { $layoutHandle = str_replace('{{TYPE}}', $typeId, self::PRODUCT_TYPE_LAYOUT_HANDLE); $this->_layoutHandles[$typeId . '_products'] = $layoutHandle; - $this->_specificEntitiesLayoutHandles[$typeId . '_products'] = self::SINGLE_PRODUCT_LAYOUT_HANLDE; + $this->_specificEntitiesLayoutHandles[$typeId . '_products'] = self::SINGLE_PRODUCT_LAYOUT_HANDLE; } } diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/actions-bar/_store-switcher.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/actions-bar/_store-switcher.less index 2ae0f3d2430bb87b4c47891dae8f0fdff569dbac..80bebb22a90434cfebd9ad46b0545536f03f8a1e 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/actions-bar/_store-switcher.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/actions-bar/_store-switcher.less @@ -42,6 +42,14 @@ max-height: 250px; overflow-y: auto; padding-top: .25em; + &::-webkit-scrollbar { + -webkit-appearance: none; + width: 7px; + } + &::-webkit-scrollbar-thumb { + border-radius: 4px; + background-color: rgba(0, 0, 0, .5); + } li { border: 0; diff --git a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_currency-addon.less b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_currency-addon.less index 0c33b8d3b8a02c5052816ff30e95cb2e4c6e39cf..df184f0f232ccf463c60faf75617eab6c3c9733f 100644 --- a/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_currency-addon.less +++ b/app/design/adminhtml/Magento/backend/Magento_ConfigurableProduct/web/css/source/module/components/_currency-addon.less @@ -10,7 +10,8 @@ @currency-addon-symbol__border-color: @field-control__border-color; @currency-addon-symbol__color: @color-gray52; @currency-addon-symbol__height: @field-control__height; -@currency-addon-symbol__width: 1.6rem; +@currency-addon-symbol__width: 2.6rem; + // // Common @@ -18,10 +19,30 @@ .currency-addon { position: relative; + border: 1px solid #adadad; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + -webkit-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; + display: inline-flex; + flex-flow: row nowrap; + width: 100%; + position: relative; .admin__control-text { - border-width: 1px 1px 1px 0; - padding-left: @currency-addon-symbol__width + .2; + appearence: none; + -webkit-flex-grow: 1; + flex-grow: 1; + -ms-flex-order: 1; + -webkit-order: 1; + order: 1; + -webkit-flex-shrink: 1; + flex-shrink: 1; + background-color: transparent; + border-color: transparent; + box-shadow: none; + vertical-align: top; &:focus { + .currency-symbol { @@ -30,18 +51,29 @@ } } + label.error { + position: absolute; + left: 0; + top: 33px; + } + .currency-symbol { border: solid @currency-addon-symbol__border-color; - border-width: 0 0 0 1px; + border-width: 0; box-sizing: border-box; color: @currency-addon-symbol__color; height: @currency-addon-symbol__height; - left: 0; padding: 7px 0 0 @indent__xs; - position: absolute; - top: 0; + position: static; transition: @smooth__border-color; - width: @currency-addon-symbol__width; + -webkit-flex-basis: auto; + flex-basis: auto; + -webkit-flex-grow: 0; + flex-grow: 0; + -webkit-flex-shrink: 0; + flex-shrink: 0; + z-index: 1; + order: 0; } ._error & { diff --git a/app/design/adminhtml/Magento/backend/web/css/source/_structure.less b/app/design/adminhtml/Magento/backend/web/css/source/_structure.less index 098d43e25757efe6ea6dd04ab468e86db4a0f08e..a74642152ec33dbf907b85e4cd258bacea856513 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/_structure.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/_structure.less @@ -36,6 +36,7 @@ body { // ToDo UI: should be moved to messages .notices-wrapper { margin: 0 3rem; + min-height: 5rem; .messages { margin-bottom: 0; } diff --git a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less index 51b02a76d0d49dcd78b159e8f636242224e6926b..2dd8463308a2cecf51ebe52f27bc7e2c5a044ef7 100644 --- a/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Catalog/web/css/source/_module.less @@ -497,6 +497,16 @@ } } + // + // Category page 1 column layout + // --------------------------------------------- + + .catalog-category-view.page-layout-1column { + .column.main { + min-height: inherit; + } + } + } // diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_cart.less index 871c9e63490daf0f0a2de452cef89418e2a68772..bb14a3c2521b04ee223c2857a60c8e55a5c2e177 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/_cart.less @@ -80,6 +80,10 @@ margin-bottom: 0; overflow: inherit; } + + .discount.coupon { + display: none; + } } // Products table diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_order-summary.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_order-summary.less index 5da1faeb8216e589867c7dd511f30611de39b5e1..43c2ad50c7a6fae89bf8dbcc4c1d9c1ec3762bfa 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_order-summary.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_order-summary.less @@ -49,6 +49,10 @@ } } + .discount.coupon { + display: none; + } + .grand.incl { & + .grand.excl { .mark, diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less index 92225804ecc044166f2792ced298159ebb0bd617..6f7da202c16e62f7c303627781ef7902f1b834a8 100644 --- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less @@ -566,6 +566,16 @@ } } } + + // + // Category page 1 column layout + // --------------------------------------------- + + .catalog-category-view.page-layout-1column { + .column.main { + min-height: inherit; + } + } } // diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less index b619391402016057de197191149656ce69fec370..4b8db9202b1958750e38f80387b8f20b1544d339 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less @@ -166,6 +166,10 @@ text-align: left; } } + + .discount.coupon { + display: none; + } } // Products table diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less index ec2e95b7d820e652274edd2f8e8f953cd896d899..5ecc4d4713bf1b36e1d68750f84c6913bef2d602 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less @@ -49,6 +49,10 @@ } } + .discount.coupon { + display: none; + } + .grand.incl { & + .grand.excl { .mark, diff --git a/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less index 94d7c7e4e5bff5618351cf61e2b8f54176f57197..9ccd6c190ec0ef480f1cf5ba0ce6322b11ef3f87 100644 --- a/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less @@ -67,6 +67,7 @@ border-bottom-left-radius: 0; border-top-left-radius: 0; margin-left: -1px; + white-space: nowrap; } } } diff --git a/composer.json b/composer.json index affd28d1fe133bdc01e8ceb0fdaecbb42440349b..fbc378d01a5cda35f60c60f6aa1db06db7533c48 100644 --- a/composer.json +++ b/composer.json @@ -59,6 +59,7 @@ "ext-dom": "*", "ext-simplexml": "*", "ext-mcrypt": "*", + "ext-bcmath": "*", "ext-hash": "*", "ext-curl": "*", "ext-iconv": "*", diff --git a/composer.lock b/composer.lock index 0f16d512409c033227ab24d0550291a0d3258187..2f9c7d47e323b51f2120dd441b1dc7ff86ba4eef 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "a6f1ec648029ca7b40870c3356fa955c", + "content-hash": "009029e4e58a3802cc9ad92333fad729", "packages": [ { "name": "braintree/braintree_php", @@ -6498,6 +6498,7 @@ "ext-dom": "*", "ext-simplexml": "*", "ext-mcrypt": "*", + "ext-bcmath": "*", "ext-hash": "*", "ext-curl": "*", "ext-iconv": "*", diff --git a/dev/tests/acceptance/tests/functional.suite.dist.yml b/dev/tests/acceptance/tests/functional.suite.dist.yml index f15d66d983a714920a0f0c0c647163e2a9d667c1..432151bdf565c17c662f771e6721422994afad7f 100644 --- a/dev/tests/acceptance/tests/functional.suite.dist.yml +++ b/dev/tests/acceptance/tests/functional.suite.dist.yml @@ -25,7 +25,7 @@ modules: config: \Magento\FunctionalTestingFramework\Module\MagentoWebDriver: url: "%MAGENTO_BASE_URL%" - backend_name: admin + backend_name: "%MAGENTO_BACKEND_NAME%" browser: 'chrome' window_size: maximize username: "%MAGENTO_ADMIN_USERNAME%" diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Customer/Cest/AdminCreateCustomerCest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Customer/Cest/AdminCreateCustomerCest.xml index b0a4cb06c0842fd7c178c6877294123fd430f36e..4e446a838d327c44490731aff20fe4ff4b681513 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Customer/Cest/AdminCreateCustomerCest.xml +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/Customer/Cest/AdminCreateCustomerCest.xml @@ -21,6 +21,7 @@ <testCaseId value="MAGETWO-72095"/> <group value="customer"/> <group value="create"/> + <group value="skip"/> </annotations> <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> <fillField userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" selector="{{AdminLoginFormSection.username}}" stepKey="fillUsername"/> diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index 09f6362c833d4de773bc6702fc0f5a28c614b473..9dab97621f2f54de8da46ab592a3f79fb765b2d0 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -326,6 +326,32 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract } } + /** + * Test that Product Repository can correctly create simple product, if product type not specified in request. + * + * @return void + */ + public function testCreateWithoutSpecifiedType() + { + $price = 3.62; + $weight = 12.2; + $sku = 'simple_product_without_specified_type'; + $product = [ + 'sku' => $sku, + 'name' => 'Simple Product Without Specified Type', + 'price' => $price, + 'weight' => $weight, + 'attribute_set_id' => 4, + ]; + $response = $this->saveProduct($product); + $this->assertSame($sku, $response[ProductInterface::SKU]); + $this->assertSame(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE, $response[ProductInterface::TYPE_ID]); + $this->assertSame($price, $response[ProductInterface::PRICE]); + $this->assertSame($weight, $response[ProductInterface::WEIGHT]); + //Clean up. + $this->deleteProduct($product[ProductInterface::SKU]); + } + /** * @param array $fixtureProduct * diff --git a/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerMetadataTest.php b/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerMetadataTest.php index 4ee936f83f537c0940f04fb2dae19680ae8d2736..f2632aa1481e4752e47e2af03ace6e51660c2a97 100644 --- a/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerMetadataTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Customer/Api/CustomerMetadataTest.php @@ -63,7 +63,7 @@ class CustomerMetadataTest extends WebapiAbstract Customer::FIRSTNAME, [ AttributeMetadata::FRONTEND_INPUT => 'text', - AttributeMetadata::INPUT_FILTER => '', + AttributeMetadata::INPUT_FILTER => 'trim', AttributeMetadata::STORE_LABEL => 'First Name', AttributeMetadata::MULTILINE_COUNT => 0, AttributeMetadata::VALIDATION_RULES => [ diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertCartPerCustomer.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertCartPerCustomer.php index 2442bd8fe1f5f7a04f05410f57b90e8c0f8dffab..1aee48ad307adf1f9ecc5775c8b23c606706ae99 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertCartPerCustomer.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertCartPerCustomer.php @@ -53,7 +53,10 @@ class AssertCartPerCustomer extends AbstractConstraint ['customer' => $customer] )->run(); \PHPUnit_Framework_Assert::assertEquals( - sprintf(self::WELCOME_MESSAGE, $customer->getFirstname()), + sprintf( + self::WELCOME_MESSAGE, + $customer->getFirstname() . ' ' . $customer->getLastname() + ), $cmsIndex->getLinksBlock()->getWelcomeText(), 'Customer welcome message is wrong.' ); diff --git a/dev/tests/integration/etc/di/preferences/ce.php b/dev/tests/integration/etc/di/preferences/ce.php index 0e6f90d75f31122f57d849c00361adc8a4fb2bff..d5aaa7e730826376a130054a65d18ffc084c91e1 100644 --- a/dev/tests/integration/etc/di/preferences/ce.php +++ b/dev/tests/integration/etc/di/preferences/ce.php @@ -16,6 +16,8 @@ return [ \Magento\Framework\App\Response\Http::class => \Magento\TestFramework\Response::class, \Magento\Framework\Interception\PluginListInterface::class => \Magento\TestFramework\Interception\PluginList::class, + \Magento\Framework\Interception\ObjectManager\ConfigInterface::class => + \Magento\TestFramework\ObjectManager\Config::class, \Magento\Framework\Interception\ObjectManager\Config\Developer::class => \Magento\TestFramework\ObjectManager\Config::class, \Magento\Framework\View\LayoutInterface::class => \Magento\TestFramework\View\Layout::class, diff --git a/dev/tests/integration/testsuite/Magento/Backend/Model/MenuTest.php b/dev/tests/integration/testsuite/Magento/Backend/Model/MenuTest.php index a05359cd8dd31b9f38d761a30f14e0d0294ca5e3..1bebca0236bef2cbc9f371eb5fad8dabb8140aeb 100644 --- a/dev/tests/integration/testsuite/Magento/Backend/Model/MenuTest.php +++ b/dev/tests/integration/testsuite/Magento/Backend/Model/MenuTest.php @@ -96,6 +96,7 @@ class MenuTest extends \PHPUnit\Framework\TestCase 'title' => 'Extended System', 'module' => 'Magento_Backend', 'resource' => 'Magento_Backend::system3', + 'dependsOnConfig' => 'dev/test/system', ] ) ); @@ -114,12 +115,13 @@ class MenuTest extends \PHPUnit\Framework\TestCase 'Magento_Backend::system3' ); $serializedString = $menu->serialize(); - $expected = '[{"parent_id":null,"module_name":"Magento_Backend","sort_index":null,"depends_on_config":null,' + $expected = '[{"parent_id":null,"module":"Magento_Backend","sort_index":null,' + . '"dependsOnConfig":"dev\/test\/system",' . '"id":"Magento_Backend::system3","resource":"Magento_Backend::system3","path":"","action":null,' - . '"depends_on_module":null,"tooltip":"","title":"Extended System",' - . '"target":null,"sub_menu":[{"parent_id":null,"module_name":"Magento_Backend","sort_index":null,' - . '"depends_on_config":null,"id":"Magento_Backend::system3_acl","resource":"Magento_Backend::system3_acl",' - . '"path":"","action":"admin\/backend\/acl\/index","depends_on_module":null,"tooltip":"","title":"Acl",' + . '"dependsOnModule":null,"toolTip":null,"title":"Extended System",' + . '"target":null,"sub_menu":[{"parent_id":null,"module":"Magento_Backend","sort_index":null,' + . '"dependsOnConfig":null,"id":"Magento_Backend::system3_acl","resource":"Magento_Backend::system3_acl",' + . '"path":"","action":"admin\/backend\/acl\/index","dependsOnModule":null,"toolTip":null,"title":"Acl",' . '"target":null,"sub_menu":null}]}]'; $this->assertEquals($expected, $serializedString); } @@ -129,12 +131,12 @@ class MenuTest extends \PHPUnit\Framework\TestCase */ public function testUnserialize() { - $serializedMenu = '[{"parent_id":null,"module_name":"Magento_Backend","sort_index":null,' - . '"depends_on_config":null,"id":"Magento_Backend::system3","resource":"Magento_Backend::system3",' - . '"path":"","action":null,"depends_on_module":null,"tooltip":"","title":"Extended System",' - . '"target":null,"sub_menu":[{"parent_id":null,"module_name":"Magento_Backend","sort_index":null,' - . '"depends_on_config":null,"id":"Magento_Backend::system3_acl","resource":"Magento_Backend::system3_acl",' - . '"path":"","action":"admin\/backend\/acl\/index","depends_on_module":null,"tooltip":"","title":"Acl",' + $serializedMenu = '[{"parent_id":null,"module":"Magento_Backend","sort_index":null,' + . '"dependsOnConfig":"dev\/test","id":"Magento_Backend::system3","resource":"Magento_Backend::system3",' + . '"path":"","action":null,"dependsOnModule":null,"toolTip":null,"title":"Extended System",' + . '"target":null,"sub_menu":[{"parent_id":null,"module":"Magento_Backend","sort_index":null,' + . '"dependsOnConfig":null,"id":"Magento_Backend::system3_acl","resource":"Magento_Backend::system3_acl",' + . '"path":"","action":"admin\/backend\/acl\/index","dependsOnModule":null,"toolTip":null,"title":"Acl",' . '"target":null,"sub_menu":null}]}]'; /** @var Menu $menu */ $menu = $this->objectManager->get(\Magento\Backend\Model\MenuFactory::class)->create(); @@ -142,30 +144,30 @@ class MenuTest extends \PHPUnit\Framework\TestCase $expected = [ [ 'parent_id' => null, - 'module_name' => 'Magento_Backend', + 'module' => 'Magento_Backend', 'sort_index' => null, - 'depends_on_config' => null, + 'dependsOnConfig' => 'dev/test', 'id' => 'Magento_Backend::system3', 'resource' => 'Magento_Backend::system3', 'path' => '', 'action' => null, - 'depends_on_module' => null, - 'tooltip' => '', + 'dependsOnModule' => null, + 'toolTip' => null, 'title' => 'Extended System', 'target' => null, 'sub_menu' => [ [ 'parent_id' => null, - 'module_name' => 'Magento_Backend', + 'module' => 'Magento_Backend', 'sort_index' => null, - 'depends_on_config' => null, + 'dependsOnConfig' => null, 'id' => 'Magento_Backend::system3_acl', 'resource' => 'Magento_Backend::system3_acl', 'path' => '', 'action' => 'admin/backend/acl/index', - 'depends_on_module' => null, - 'tooltip' => '', + 'dependsOnModule' => null, + 'toolTip' => null, 'title' => 'Acl', 'sub_menu' => null, 'target' => null diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php index 9518e9c0cdf4fbf476e2cf554ce566afc3547714..330487b757f617bec675e0a87ddba0a9c99049e1 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductRepositoryTest.php @@ -49,4 +49,25 @@ class ProductRepositoryTest extends \PHPUnit\Framework\TestCase $updatedProduct->load($productId); self::assertSame($newSku, $updatedProduct->getSku()); } + + /** + * Check Product Repository able to correctly create product without specified type. + * + * @magentoDbIsolation enabled + */ + public function testCreateWithoutSpecifiedType() + { + /** @var Product $product */ + $product = Bootstrap::getObjectManager()->get(ProductFactory::class)->create(); + $product->setName('Simple without specified type'); + $product->setSku('simple_without_specified_type'); + $product->setPrice(1.12); + $product->setWeight(1.23); + $product->setAttributeSetId(4); + $product = $this->productRepository->save($product); + + self::assertSame('1.1200', $product->getPrice()); + self::assertSame('1.2300', $product->getWeight()); + self::assertSame('simple', $product->getTypeId()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Api/PaymentInformationManagementTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Api/PaymentInformationManagementTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d2ade6296e71cb97a9ce26acfb90836755ef2ae3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/Api/PaymentInformationManagementTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Checkout\Api; + +use Braintree\Result\Error; +use Magento\Braintree\Gateway\Http\Client\TransactionSale; +use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\State; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartInterface; +use Magento\Quote\Api\Data\PaymentInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +class PaymentInformationManagementTest extends TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var TransactionSale|MockObject + */ + private $client; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + + $this->client = $this->getMockBuilder(TransactionSale::class) + ->disableOriginalConstructor() + ->getMock(); + $this->objectManager->addSharedInstance($this->client, TransactionSale::class); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->objectManager->removeSharedInstance(TransactionSale::class); + parent::tearDown(); + } + + /** + * Checks a case when payment method triggers an error during place order flow and + * error messages from payment gateway should be mapped. + * Error messages might be specific for different areas. + * + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Checkout/_files/quote_with_shipping_method.php + * @magentoConfigFixture current_store payment/braintree/active 1 + * @dataProvider getErrorPerAreaDataProvider + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + */ + public function testSavePaymentInformationAndPlaceOrderWithErrors(string $area, string $errorMessage) + { + /** @var State $state */ + $state = $this->objectManager->get(State::class); + $state->setAreaCode($area); + + $quote = $this->getQuote('test_order_1'); + + /** @var PaymentInterface $payment */ + $payment = $this->objectManager->create(PaymentInterface::class); + $payment->setMethod(ConfigProvider::CODE); + + $errors = [ + 'errors' => [ + [ + 'code' => 'fake_code', + 'attribute' => 'base', + 'message' => 'Error message should not be mapped.' + ], + [ + 'code' => 81802, + 'attribute' => 'base', + 'message' => 'Company is too long.' + ], + [ + 'code' => 91511, + 'attribute' => 'base', + 'message' => 'Customer does not have any credit cards.' + ] + ] + ]; + $response = new Error(['errors' => $errors]); + + $this->client->method('placeRequest') + ->willReturn(['object' => $response]); + + $this->expectExceptionMessage($errorMessage); + + /** @var PaymentInformationManagementInterface $paymentInformationManagement */ + $paymentInformationManagement = $this->objectManager->get(PaymentInformationManagementInterface::class); + $paymentInformationManagement->savePaymentInformationAndPlaceOrder( + $quote->getId(), + $payment + ); + } + + /** + * Gets list of areas with specific error messages. + * + * @return array + */ + public function getErrorPerAreaDataProvider() + { + $globalAreaError = 'Company is too long.'; + return [ + ['area' => 'frontend', 'error' => $globalAreaError], + ['area' => 'adminhtml', 'error' => $globalAreaError . PHP_EOL . 'Customer does not have any credit cards.'], + ]; + } + + /** + * Retrieves quote by provided order ID. + * + * @param string $reservedOrderId + * @return CartInterface + */ + private function getQuote(string $reservedOrderId) : CartInterface + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $items = $quoteRepository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/BlockTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/BlockTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9b2bb67c55da1de625657b4cb862a5ceb3b92816 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/BlockTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cms\Model; + +use Magento\Cms\Api\BlockRepositoryInterface; +use Magento\Cms\Model\BlockFactory; +use Magento\Cms\Model\ResourceModel\Block; +use Magento\Framework\ObjectManagerInterface; +use Magento\Framework\Stdlib\DateTime\DateTime; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * @magentoAppArea adminhtml + */ +class BlockTest extends TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var Block + */ + private $blockResource; + + /** + * @var BlockFactory + */ + private $blockFactory; + + /** + * @var BlockRepositoryInterface + */ + private $blockRepository; + + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + + /** @var BlockFactory $blockFactory */ + /** @var Block $blockResource */ + $this->blockResource = $this->objectManager->create(Block::class); + $this->blockFactory = $this->objectManager->create(BlockFactory::class); + $this->blockRepository = $this->objectManager->create(BlockRepositoryInterface::class); + } + + /** + * Test UpdateTime + * @param array $blockData + * @throws \Exception + * @magentoDbIsolation enabled + * @dataProvider testUpdateTimeDataProvider + */ + public function testUpdateTime(array $blockData) + { + # Prepare and save the temporary block + $tempBlock = $this->blockFactory->create(); + $tempBlock->setData($blockData); + $this->blockResource->save($tempBlock); + + # Load previously created block and compare update_time field + $block = $this->blockRepository->getById($tempBlock->getId()); + $date = $this->objectManager->get(DateTime::class)->date(); + $this->assertEquals($date, $block->getUpdateTime()); + } + + /** + * Data provider "testUpdateTime" method + * @return array + */ + public function testUpdateTimeDataProvider() + { + return [ + [ + 'data' => [ + 'title' => 'Test title', + 'stores' => [0], + 'identifier' => 'test-identifier', + 'content' => 'Test content', + 'is_active' => 1 + ] + ] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php index 7a04ddce8fa083e35f1069db561741a1d54213e7..c8040861b08ebb2a37b59741846dbc76607a03f6 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/PageTest.php @@ -6,17 +6,13 @@ namespace Magento\Cms\Model; use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Framework\Stdlib\DateTime\DateTime; /** * @magentoAppArea adminhtml */ class PageTest extends \PHPUnit\Framework\TestCase { - /** - * @var \Magento\Cms\Model\Page - */ - protected $model; - protected function setUp() { $user = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( @@ -51,15 +47,14 @@ class PageTest extends \PHPUnit\Framework\TestCase */ public function testUpdateTime() { - $updateTime = '2016-09-01 00:00:00'; $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); /** @var \Magento\Cms\Model\Page $page */ $page = $objectManager->create(\Magento\Cms\Model\Page::class); $page->setData(['title' => 'Test', 'stores' => [1]]); - $page->setUpdateTime($updateTime); $page->save(); $page = $objectManager->get(PageRepositoryInterface::class)->getById($page->getId()); - $this->assertEquals($updateTime, $page->getUpdateTime()); + $date = $objectManager->get(DateTime::class)->date(); + $this->assertEquals($date, $page->getUpdateTime()); } public function generateIdentifierFromTitleDataProvider() diff --git a/dev/tests/integration/testsuite/Magento/Directory/Model/PriceCurrencyTest.php b/dev/tests/integration/testsuite/Magento/Directory/Model/PriceCurrencyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..fd6b8577eaf9809da9b71b04c5904fa138ca414b --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Directory/Model/PriceCurrencyTest.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Directory\Model; + +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Provide tests for PriceCurrency model. + */ +class PriceCurrencyTest extends TestCase +{ + /** + * Test subject. + * + * @var PriceCurrency + */ + private $priceCurrency; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->priceCurrency = Bootstrap::getObjectManager()->get(PriceCurrency::class); + } + + /** + * Check PriceCurrency::format() doesn't depend on currency rate configuration. + * @return void + */ + public function testFormat() + { + self::assertSame( + '<span class="price">AFN10.00</span>', + $this->priceCurrency->format(10, true, 2, null, 'AFN') + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/Session/SidResolverTest.php b/dev/tests/integration/testsuite/Magento/Framework/Session/SidResolverTest.php index 5af3e52420f113124e21f139cc19ed19e4fe56ed..2cfb8b47da7c32a20122ca350d55d9ed640548c5 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Session/SidResolverTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Session/SidResolverTest.php @@ -112,6 +112,7 @@ class SidResolverTest extends \PHPUnit\Framework\TestCase $this->request->getQuery()->set($this->model->getSessionIdQueryParam($this->session), $testSid); } $this->assertEquals($sid, $this->model->getSid($this->session)); + $this->assertEquals($useFrontedSid, $this->model->getUseSessionInUrl()); } /** @@ -150,10 +151,42 @@ class SidResolverTest extends \PHPUnit\Framework\TestCase $this->assertTrue($this->model->getUseSessionVar()); } - public function testSetGetUseSessionInUrl() + /** + * Variations of Use SID on frontend value. + * + * @return array + */ + public function dataProviderSessionInUrl() + { + return [ + [true], + [false], + ]; + } + + /** + * Testing "Use SID in URLs" flag. + * Checking that the method returns config value if not explicitly + * overridden. + * + * @param bool $configValue Use SID on frontend config value. + * @dataProvider dataProviderSessionInUrl + */ + public function testSetGetUseSessionInUrl($configValue) { - $this->assertTrue($this->model->getUseSessionInUrl()); - $this->model->setUseSessionInUrl(false); - $this->assertFalse($this->model->getUseSessionInUrl()); + $this->scopeConfig->expects( + $this->any() + )->method( + 'getValue' + )->with( + \Magento\Framework\Session\SidResolver::XML_PATH_USE_FRONTEND_SID, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + )->will( + $this->returnValue($configValue) + ); + + $this->assertEquals($configValue, $this->model->getUseSessionInUrl()); + $this->model->setUseSessionInUrl(!$configValue); + $this->assertEquals(!$configValue, $this->model->getUseSessionInUrl()); } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Pdf/AbstractPdfTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Pdf/AbstractPdfTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a70d4a3b6f0c1fe313a8df65982f87e9c8b22ef5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Pdf/AbstractPdfTest.php @@ -0,0 +1,86 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Sales\Model\Order\Pdf; + +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Tests Sales Order PDF abstract model. + * + * @see \Magento\Sales\Model\Order\Pdf\AbstarctPdf + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class AbstractPdfTest extends \PHPUnit\Framework\TestCase +{ + /** + * Tests Draw lines method. + * Test case when text block cover more than one page. + */ + public function testDrawLineBlocks() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + // Setup most constructor dependencies + $paymentData = $objectManager->create(\Magento\Payment\Helper\Data::class); + $string = $objectManager->create(\Magento\Framework\Stdlib\StringUtils::class); + $scopeConfig = $objectManager->create(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $filesystem = $objectManager->create(\Magento\Framework\Filesystem::class); + $config = $objectManager->create(\Magento\Sales\Model\Order\Pdf\Config::class); + $pdfTotalFactory = $objectManager->create(\Magento\Sales\Model\Order\Pdf\Total\Factory::class); + $pdfItemsFactory = $objectManager->create(\Magento\Sales\Model\Order\Pdf\ItemsFactory::class); + $locale = $objectManager->create(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class); + $translate = $objectManager->create(\Magento\Framework\Translate\Inline\StateInterface::class); + $addressRenderer = $objectManager->create(\Magento\Sales\Model\Order\Address\Renderer::class); + + // Test model + /** @var \Magento\Sales\Model\Order\Pdf\AbstractPdf|MockObject $model */ + $model = $this->getMockForAbstractClass( + \Magento\Sales\Model\Order\Pdf\AbstractPdf::class, + [ + $paymentData, + $string, + $scopeConfig, + $filesystem, + $config, + $pdfTotalFactory, + $pdfItemsFactory, + $locale, + $translate, + $addressRenderer, + ], + '', + true, + true, + true, + ['getPdf', '_getPdf'] + ); + $pdf = new \Zend_Pdf(); + $model->expects($this->any())->method('getPdf')->will($this->returnValue($pdf)); + $model->expects($this->any())->method('_getPdf')->will($this->returnValue($pdf)); + + /** Generate multiline block, that cover more than one page */ + $lines = []; + for ($lineNumber = 1; $lineNumber <= 100; $lineNumber++) { + $lines[] = [[ + 'feed' => 0, + 'font_size' => 10, + 'text' => 'Text line ' . $lineNumber, + ]]; + } + $draw = [[ + 'height' => 12, + 'lines' => $lines, + ]]; + + $page = $model->newPage(['page_size' => \Zend_Pdf_Page::SIZE_A4]); + + $model->drawLineBlocks($page, $draw); + $this->assertEquals( + 3, + count($pdf->pages) + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php index 7166779621001bec29d4cb552362ad6835d9dcbc..52268dc96d8a325720b1f824cc0687b28d706a5f 100644 --- a/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Security/Model/Plugin/AuthSessionTest.php @@ -40,6 +40,11 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase */ protected $dateTime; + /** + * @var \Magento\Security\Model\ConfigInterface + */ + protected $securityConfig; + /** * Set up */ @@ -54,8 +59,9 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase $this->authSession = $this->objectManager->create(\Magento\Backend\Model\Auth\Session::class); $this->adminSessionInfo = $this->objectManager->create(\Magento\Security\Model\AdminSessionInfo::class); $this->auth->setAuthStorage($this->authSession); - $this->adminSessionsManager = $this->objectManager->create(\Magento\Security\Model\AdminSessionsManager::class); + $this->adminSessionsManager = $this->objectManager->get(\Magento\Security\Model\AdminSessionsManager::class); $this->dateTime = $this->objectManager->create(\Magento\Framework\Stdlib\DateTime::class); + $this->securityConfig = $this->objectManager->create(\Magento\Security\Model\ConfigInterface::class); } /** @@ -73,7 +79,42 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase /** * Test of prolong user action + * session manager will not trigger new prolong if previous prolong was less than X sec ago + * X - is calculated based on current admin session lifetime * + * @see \Magento\Security\Model\AdminSessionsManager::lastProlongIsOldEnough + * @magentoDbIsolation enabled + */ + public function testConsecutiveProcessProlong() + { + $this->auth->login( + \Magento\TestFramework\Bootstrap::ADMIN_NAME, + \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD + ); + $sessionId = $this->authSession->getSessionId(); + $prolongsDiff = log($this->securityConfig->getAdminSessionLifetime()) - 2; // X from comment above + $dateInPast = $this->dateTime->formatDate($this->authSession->getUpdatedAt() - $prolongsDiff); + $this->adminSessionsManager->getCurrentSession() + ->setData( + 'updated_at', + $dateInPast + ) + ->save(); + $this->adminSessionInfo->load($sessionId, 'session_id'); + $oldUpdatedAt = $this->adminSessionInfo->getUpdatedAt(); + $this->authSession->prolong(); + $this->adminSessionInfo->load($sessionId, 'session_id'); + $updatedAt = $this->adminSessionInfo->getUpdatedAt(); + + $this->assertSame(strtotime($oldUpdatedAt), strtotime($updatedAt)); + } + + /** + * Test of prolong user action + * session manager will trigger new prolong if previous prolong was more than X sec ago + * X - is calculated based on current admin session lifetime + * + * @see \Magento\Security\Model\AdminSessionsManager::lastProlongIsOldEnough * @magentoDbIsolation enabled */ public function testProcessProlong() @@ -83,7 +124,8 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD ); $sessionId = $this->authSession->getSessionId(); - $dateInPast = $this->dateTime->formatDate($this->authSession->getUpdatedAt() - 100); + $prolongsDiff = 4 * log($this->securityConfig->getAdminSessionLifetime()) + 2; // X from comment above + $dateInPast = $this->dateTime->formatDate($this->authSession->getUpdatedAt() - $prolongsDiff); $this->adminSessionsManager->getCurrentSession() ->setData( 'updated_at', @@ -95,6 +137,7 @@ class AuthSessionTest extends \PHPUnit\Framework\TestCase $this->authSession->prolong(); $this->adminSessionInfo->load($sessionId, 'session_id'); $updatedAt = $this->adminSessionInfo->getUpdatedAt(); - $this->assertGreaterThan($oldUpdatedAt, $updatedAt); + + $this->assertGreaterThan(strtotime($oldUpdatedAt), strtotime($updatedAt)); } } diff --git a/dev/tests/integration/testsuite/Magento/Shipping/Helper/DataTest.php b/dev/tests/integration/testsuite/Magento/Shipping/Helper/DataTest.php index eaac89cc6851beec44e52b46bffa51373cd51fc0..99a28812a12d5737d0bb876f45df49c15c0226f6 100644 --- a/dev/tests/integration/testsuite/Magento/Shipping/Helper/DataTest.php +++ b/dev/tests/integration/testsuite/Magento/Shipping/Helper/DataTest.php @@ -5,16 +5,18 @@ */ namespace Magento\Shipping\Helper; +use Magento\Store\Model\StoreManagerInterface; + class DataTest extends \PHPUnit\Framework\TestCase { /** * @var \Magento\Shipping\Helper\Data */ - protected $_helper = null; + private $helper; protected function setUp() { - $this->_helper = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + $this->helper = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( \Magento\Shipping\Helper\Data::class ); } @@ -31,33 +33,77 @@ class DataTest extends \PHPUnit\Framework\TestCase { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $constructArgs = []; - if (\Magento\Sales\Model\Order\Shipment::class == $modelName) { - $orderRepository = $this->_getMockOrderRepository($code); + if (\Magento\Sales\Model\Order\Shipment::class === $modelName) { + $orderRepository = $this->getMockOrderRepository($code); + $constructArgs['orderRepository'] = $orderRepository; + } elseif (\Magento\Sales\Model\Order\Shipment\Track::class === $modelName) { + $shipmentRepository = $this->getMockShipmentRepository($code); + $constructArgs['shipmentRepository'] = $shipmentRepository; + } + + $model = $objectManager->create($modelName, $constructArgs); + $model->{$getIdMethod}($entityId); + + if (\Magento\Sales\Model\Order::class === $modelName) { + $model->setProtectCode($code); + } + if (\Magento\Sales\Model\Order\Shipment\Track::class === $modelName) { + $model->setParentId(1); + } + + $actual = $this->helper->getTrackingPopupUrlBySalesModel($model); + $this->assertEquals($expected, $actual); + } + + /** + * From the admin panel with custom URL we should have generated frontend URL + * + * @param string $modelName + * @param string $getIdMethod + * @param int $entityId + * @param string $code + * @param string $expected + * @magentoAppArea adminhtml + * @magentoConfigFixture admin_store web/unsecure/base_link_url http://admin.localhost/ + * @dataProvider getTrackingPopupUrlBySalesModelDataProvider + */ + public function testGetTrackingPopupUrlBySalesModelFromAdmin($modelName, $getIdMethod, $entityId, $code, $expected) + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + + /** @var StoreManagerInterface $storeManager */ + $storeManager = $objectManager->create(StoreManagerInterface::class); + $storeManager->reinitStores(); + + $constructArgs = []; + if (\Magento\Sales\Model\Order\Shipment::class === $modelName) { + $orderRepository = $this->getMockOrderRepository($code); $constructArgs['orderRepository'] = $orderRepository; - } elseif (\Magento\Sales\Model\Order\Shipment\Track::class == $modelName) { - $shipmentRepository = $this->_getMockShipmentRepository($code); + } elseif (\Magento\Sales\Model\Order\Shipment\Track::class === $modelName) { + $shipmentRepository = $this->getMockShipmentRepository($code); $constructArgs['shipmentRepository'] = $shipmentRepository; } $model = $objectManager->create($modelName, $constructArgs); $model->{$getIdMethod}($entityId); - if (\Magento\Sales\Model\Order::class == $modelName) { + if (\Magento\Sales\Model\Order::class === $modelName) { $model->setProtectCode($code); } - if (\Magento\Sales\Model\Order\Shipment\Track::class == $modelName) { + if (\Magento\Sales\Model\Order\Shipment\Track::class === $modelName) { $model->setParentId(1); } - $actual = $this->_helper->getTrackingPopupUrlBySalesModel($model); + //Frontend URL should be used there + $actual = $this->helper->getTrackingPopupUrlBySalesModel($model); $this->assertEquals($expected, $actual); } /** * @param $code - * @return \Magento\Sales\Api\OrderRepositoryInterface + * @return \Magento\Sales\Api\OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected function _getMockOrderRepository($code) + private function getMockOrderRepository($code) { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); $order = $objectManager->create(\Magento\Sales\Model\Order::class); @@ -71,10 +117,10 @@ class DataTest extends \PHPUnit\Framework\TestCase * @param $code * @return \Magento\Sales\Model\Order\ShipmentRepository|\PHPUnit_Framework_MockObject_MockObject */ - protected function _getMockShipmentRepository($code) + private function getMockShipmentRepository($code) { $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - $orderRepository = $this->_getMockOrderRepository($code); + $orderRepository = $this->getMockOrderRepository($code); $shipmentArgs = ['orderRepository' => $orderRepository]; $shipment = $objectManager->create(\Magento\Sales\Model\Order\Shipment::class, $shipmentArgs); diff --git a/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php b/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php index a55ca821cba6082fe963d7e7ae33973d5ec73286..333ffb01b24ec0133b43f5a8a8cb99fc5feaa19a 100644 --- a/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php +++ b/dev/tests/integration/testsuite/Magento/Theme/Controller/Adminhtml/System/Design/Config/SaveTest.php @@ -67,7 +67,7 @@ class SaveTest extends AbstractBackendController 'header_logo_height' => '', 'header_logo_alt' => '', 'header_welcome' => 'Default welcome msg!', - 'footer_copyright' => 'Copyright © 2013-2017 Magento, Inc. All rights reserved.', + 'footer_copyright' => 'Copyright © 2013-2018 Magento, Inc. All rights reserved.', 'footer_absolute_footer' => '', 'default_robots' => 'INDEX,FOLLOW', 'custom_instructions' => '', diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js index d71f517b5e7f9c1ccde4a6de66376bb5820faf1e..52739eec2782b071a08d7a680677939e8dd96979 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js @@ -27,7 +27,7 @@ define([ ), 'Magento_Braintree/js/view/payment/adapter': jasmine.createSpyObj( 'adapter', - ['setup', 'setConfig'] + ['setup', 'setConfig', 'showError'] ) }, braintreeCcForm; @@ -43,14 +43,17 @@ define([ }; injector.mock(mocks); injector.require(['Magento_Braintree/js/view/payment/method-renderer/cc-form'], function (Constr) { - braintreeCcForm = new Constr({ - provider: 'provName', - name: 'test', - index: 'test' - }); - - done(); + braintreeCcForm = new Constr({ + provider: 'provName', + name: 'test', + index: 'test', + item: { + title: 'Braintree' + } }); + + done(); + }); }); it('Check if payment code and message container are restored after onActiveChange call.', function () { @@ -65,5 +68,21 @@ define([ expect(braintreeCcForm.getCode()).toEqual(expectedCode); expect(braintreeCcForm.messageContainer).toEqual(expectedMessageContainer); }); + + it('Check if form validation fails when "Place Order" button should be active.', function () { + var errorMessage = 'Something went wrong.', + + /** + * Anonymous wrapper + */ + func = function () { + braintreeCcForm.clientConfig.onError({ + 'message': errorMessage + }); + }; + + expect(func).toThrow(errorMessage); + expect(braintreeCcForm.isPlaceOrderActionAllowed()).toBeTruthy(); + }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js new file mode 100644 index 0000000000000000000000000000000000000000..545daf0a330c9dc04f3a8f03bf8d7998852a299e --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/place-order-mixin.test.js @@ -0,0 +1,55 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'squire' +], function (Squire) { + 'use strict'; + + var injector = new Squire(), + mocks = { + 'Magento_Checkout/js/action/place-order': jasmine.createSpy('placeOrderAction'), + 'Magento_CheckoutAgreements/js/model/agreements-assigner': jasmine.createSpy('agreementsAssigner') + }, + defaultContext = require.s.contexts._, + mixin, + placeOrderAction; + + beforeEach(function (done) { + window.checkoutConfig = { + checkoutAgreements: { + isEnabled: true + } + }; + injector.mock(mocks); + injector.require([ + 'Magento_CheckoutAgreements/js/model/place-order-mixin', + 'Magento_Checkout/js/action/place-order' + ], function (Mixin, placeOrder) { + mixin = Mixin; + placeOrderAction = placeOrder; + done(); + }); + }); + + describe('Magento_CheckoutAgreements/js/model/place-order-mixin', function () { + it('mixin is applied to Magento_Checkout/js/action/place-order', function () { + var placeOrderMixins = defaultContext.config.config.mixins['Magento_Checkout/js/action/place-order']; + + expect(placeOrderMixins['Magento_CheckoutAgreements/js/model/place-order-mixin']).toBe(true); + }); + + it('Magento_CheckoutAgreements/js/model/agreements-assigner is called', function () { + var messageContainer = jasmine.createSpy('messageContainer'), + paymentData = {}; + + mixin(placeOrderAction)(paymentData, messageContainer); + expect(mocks['Magento_CheckoutAgreements/js/model/agreements-assigner']) + .toHaveBeenCalledWith(paymentData); + expect(mocks['Magento_Checkout/js/action/place-order']) + .toHaveBeenCalledWith(paymentData, messageContainer); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js new file mode 100644 index 0000000000000000000000000000000000000000..ed525bfd96a6c6ea57ffdbd56efd30a6ee920718 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/CheckoutAgreements/frontend/js/model/set-payment-information-mixin.test.js @@ -0,0 +1,56 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'squire' +], function (Squire) { + 'use strict'; + + var injector = new Squire(), + mocks = { + 'Magento_Checkout/js/action/set-payment-information': jasmine.createSpy('placeOrderAction'), + 'Magento_CheckoutAgreements/js/model/agreements-assigner': jasmine.createSpy('agreementsAssigner') + }, + defaultContext = require.s.contexts._, + mixin, + placeOrderAction; + + beforeEach(function (done) { + window.checkoutConfig = { + checkoutAgreements: { + isEnabled: true + } + }; + injector.mock(mocks); + injector.require([ + 'Magento_CheckoutAgreements/js/model/set-payment-information-mixin', + 'Magento_Checkout/js/action/set-payment-information' + ], function (Mixin, setPaymentInformation) { + mixin = Mixin; + placeOrderAction = setPaymentInformation; + done(); + }); + }); + + describe('Magento_CheckoutAgreements/js/model/set-payment-information-mixin', function () { + it('mixin is applied to Magento_Checkout/js/action/set-payment-information', function () { + var placeOrderMixins = defaultContext + .config.config.mixins['Magento_Checkout/js/action/set-payment-information']; + + expect(placeOrderMixins['Magento_CheckoutAgreements/js/model/set-payment-information-mixin']).toBe(true); + }); + + it('Magento_CheckoutAgreements/js/model/agreements-assigner is called', function () { + var messageContainer = jasmine.createSpy('messageContainer'), + paymentData = {}; + + mixin(placeOrderAction)(messageContainer, paymentData); + expect(mocks['Magento_CheckoutAgreements/js/model/agreements-assigner']) + .toHaveBeenCalledWith(paymentData); + expect(mocks['Magento_Checkout/js/action/set-payment-information']) + .toHaveBeenCalledWith(messageContainer, paymentData); + }); + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js index dc520c52385687b72d67602bad2cf8fd060b29ab..47e3507ea13214957d8ec0c0f9453d6fe93aa1a6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js @@ -16,6 +16,13 @@ define([ describe('paypal/js/view/payment/method-renderer/paypal-express-abstract', function () { var injector = new Squire(), + successPromise = jasmine.createSpyObj('successPromise', ['done']), + setPaymentMock = jasmine.createSpy('set-payment-information', function () { + return successPromise; + }).and.callThrough(), + validateMock = jasmine.createSpy('validate', function () { + return true; + }).and.callThrough(), mocks = { 'Magento_Checkout/js/model/quote': { billingAddress: ko.observable(), @@ -23,6 +30,10 @@ define([ paymentMethod: ko.observable(), totals: ko.observable({}) + }, + 'Magento_Checkout/js/action/set-payment-information': setPaymentMock, + 'Magento_Checkout/js/model/payment/additional-validators': { + validate: validateMock } }, paypalExpressAbstract, @@ -85,6 +96,23 @@ define([ }, 500); }); + it('setPaymentMethodAction is called before redirect to paypal', function () { + spyOn(paypalExpressAbstract, 'selectPaymentMethod'); + paypalExpressAbstract.continueToPayPal(); + expect(paypalExpressAbstract.selectPaymentMethod).toHaveBeenCalled(); + expect(validateMock).toHaveBeenCalled(); + expect(validateMock.calls.mostRecent()).toEqual(jasmine.objectContaining({ + object: mocks['Magento_Checkout/js/model/payment/additional-validators'], + args: [], + returnValue: true + })); + expect(setPaymentMock).toHaveBeenCalled(); + expect(setPaymentMock.calls.mostRecent()).toEqual(jasmine.objectContaining({ + returnValue: successPromise + })); + expect(successPromise.done).toHaveBeenCalledWith(jasmine.any(Function)); + }); + afterAll(function (done) { tplElement.remove(); done(); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js new file mode 100644 index 0000000000000000000000000000000000000000..334bf94892b792d02001e63f5ccd3d6e65175b01 --- /dev/null +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/validation/rules.test.js @@ -0,0 +1,43 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/* eslint-disable max-nested-callbacks */ +define([ + 'Magento_Ui/js/lib/validation/rules' +], function (rules) { + 'use strict'; + + describe('Magento_Ui/js/lib/validation/rules', function () { + describe('"range-words" method', function () { + it('Check on empty value', function () { + var value = '', + params = [1,3]; + + expect(rules['range-words'].handler(value, params)).toBe(false); + }); + + it('Check on redundant words', function () { + var value = 'a b c d', + params = [1,3]; + + expect(rules['range-words'].handler(value, params)).toBe(false); + }); + + it('Check with three words', function () { + var value = 'a b c', + params = [1,3]; + + expect(rules['range-words'].handler(value, params)).toBe(true); + }); + + it('Check with one word', function () { + var value = 'a', + params = [1,3]; + + expect(rules['range-words'].handler(value, params)).toBe(true); + }); + }); + }); +}); diff --git a/lib/internal/Magento/Framework/Data/Form/Filter/Trim.php b/lib/internal/Magento/Framework/Data/Form/Filter/Trim.php new file mode 100644 index 0000000000000000000000000000000000000000..0dbbcf6dbf8d056902f8be9193c2b7b0ed65d363 --- /dev/null +++ b/lib/internal/Magento/Framework/Data/Form/Filter/Trim.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +/** + * Form Input/Output Trim Filter + * + * @author Magento Core Team <core@magentocommerce.com> + */ +namespace Magento\Framework\Data\Form\Filter; + +class Trim implements \Magento\Framework\Data\Form\Filter\FilterInterface +{ + /** + * Returns the result of filtering $value + * + * @param string $value + * @return string + */ + public function inputFilter($value) + { + return trim($value, ' '); + } + + /** + * Returns the result of filtering $value + * + * @param string $value + * @return string + */ + public function outputFilter($value) + { + return $value; + } +} diff --git a/lib/internal/Magento/Framework/DomDocument/DomDocumentFactory.php b/lib/internal/Magento/Framework/DomDocument/DomDocumentFactory.php index 677e4d654e52c4334b4bee2775805a86afa3aa7e..d2e16700bc904fdbac6b5596a6c09baf50777f8a 100644 --- a/lib/internal/Magento/Framework/DomDocument/DomDocumentFactory.php +++ b/lib/internal/Magento/Framework/DomDocument/DomDocumentFactory.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Framework\DomDocument; /** diff --git a/lib/internal/Magento/Framework/Event/Test/Unit/Config/_files/valid_events.xml b/lib/internal/Magento/Framework/Event/Test/Unit/Config/_files/valid_events.xml index 5cf120adb6b9944fae021bafe59bb76015e83efd..a9f445339e3dcbd81ed6f0344a7f19515fac27d3 100644 --- a/lib/internal/Magento/Framework/Event/Test/Unit/Config/_files/valid_events.xml +++ b/lib/internal/Magento/Framework/Event/Test/Unit/Config/_files/valid_events.xml @@ -12,4 +12,7 @@ <event name="authorization_roles_save_before"> <observer name="second_name" instance="Some_Test_Value_Two" /> </event> + <event name="authorization_roles_save_before123"> + <observer name="second_name" instance="Some_Test_Value_Two" /> + </event> </config> diff --git a/lib/internal/Magento/Framework/Event/etc/events.xsd b/lib/internal/Magento/Framework/Event/etc/events.xsd index ad7c5e59da99c7952e4d387051468697d49a92b5..d656b7fdb6ed66aedfd5dab0863017ab16e9d717 100644 --- a/lib/internal/Magento/Framework/Event/etc/events.xsd +++ b/lib/internal/Magento/Framework/Event/etc/events.xsd @@ -60,11 +60,11 @@ <xs:simpleType name="eventName"> <xs:annotation> <xs:documentation> - Event name can contain only [a-zA-Z_]. + Event name can contain only [a-zA-Z0-9_]. </xs:documentation> </xs:annotation> <xs:restriction base="xs:string"> - <xs:pattern value="[a-zA-Z_]+" /> + <xs:pattern value="[a-zA-Z0-9_]+" /> </xs:restriction> </xs:simpleType> </xs:schema> diff --git a/lib/internal/Magento/Framework/Locale/Config.php b/lib/internal/Magento/Framework/Locale/Config.php index 2a623deca082f8971a5ed5e64a07667ee519c1b1..4db842acedb7e42ae75b2230f936c79b0fbd22f8 100644 --- a/lib/internal/Magento/Framework/Locale/Config.php +++ b/lib/internal/Magento/Framework/Locale/Config.php @@ -89,6 +89,7 @@ class Config implements \Magento\Framework\Locale\ConfigInterface 'sq_AL', /*Albanian (Albania)*/ 'sr_Cyrl_RS', /*Serbian (Serbia)*/ 'sv_SE', /*Swedish (Sweden)*/ + 'sv_FI', /*Swedish (Finland)*/ 'sw_KE', /*Swahili (Kenya)*/ 'th_TH', /*Thai (Thailand)*/ 'tr_TR', /*Turkish (Turkey)*/ diff --git a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php index c94de1534b29e8f70a8723f2d000862266bc9878..b57755ed7eafa51bc3361aad0f5adf0b6584916d 100644 --- a/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php +++ b/lib/internal/Magento/Framework/Model/ResourceModel/Db/Collection/AbstractCollection.php @@ -496,9 +496,9 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn /** * Join table to collection select * - * @param string $table + * @param string|array $table * @param string $cond - * @param string $cols + * @param string|array $cols * @return $this */ public function join($table, $cond, $cols = '*') diff --git a/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/InlineTest.php b/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/InlineTest.php index f9b6e47c19a8634943f3b76e1633fb82ed954c48..d5b9443788dd2ee6d131e13af6d0eb038bb594aa 100644 --- a/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/InlineTest.php +++ b/lib/internal/Magento/Framework/Phrase/Test/Unit/Renderer/InlineTest.php @@ -13,7 +13,7 @@ class InlineTest extends \PHPUnit\Framework\TestCase protected $translator; /** - * @var \Magento\Framework\Phrase\Renderer\Translate + * @var \Magento\Framework\Phrase\Renderer\Inline */ protected $renderer; diff --git a/lib/internal/Magento/Framework/Session/SidResolver.php b/lib/internal/Magento/Framework/Session/SidResolver.php index 40d985614a3c6d80c80277f979345e5e30f150b6..18f6138b661bf9c1094d6826f4ec98f2cada9516 100644 --- a/lib/internal/Magento/Framework/Session/SidResolver.php +++ b/lib/internal/Magento/Framework/Session/SidResolver.php @@ -44,10 +44,10 @@ class SidResolver implements SidResolverInterface /** * Use session in URL flag * - * @var bool + * @var bool|null * @see \Magento\Framework\UrlInterface */ - protected $_useSessionInUrl = true; + protected $_useSessionInUrl; /** * @var string @@ -82,10 +82,7 @@ class SidResolver implements SidResolverInterface public function getSid(SessionManagerInterface $session) { $sidKey = null; - $useSidOnFrontend = $this->scopeConfig->getValue( - self::XML_PATH_USE_FRONTEND_SID, - $this->_scopeType - ); + $useSidOnFrontend = $this->getUseSessionInUrl(); if ($useSidOnFrontend && $this->request->getQuery( $this->getSessionIdQueryParam($session), false @@ -147,13 +144,22 @@ class SidResolver implements SidResolverInterface } /** - * Retrieve use session in URL flag + * Retrieve use session in URL flag. * * @return bool * @SuppressWarnings(PHPMD.BooleanGetMethodName) */ public function getUseSessionInUrl() { + if ($this->_useSessionInUrl === null) { + //Using config value by default, can be overridden by using the + //setter. + $this->_useSessionInUrl = (bool)$this->scopeConfig->getValue( + self::XML_PATH_USE_FRONTEND_SID, + $this->_scopeType + ); + } + return $this->_useSessionInUrl; } } diff --git a/lib/internal/Magento/Framework/Test/Unit/DomDocument/DomDocumentFactoryTest.php b/lib/internal/Magento/Framework/Test/Unit/DomDocument/DomDocumentFactoryTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e3ea74339e0436567166a6520f31e06b45563a47 --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/DomDocument/DomDocumentFactoryTest.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Framework\Test\Unit\DomDocument; + +use Magento\Framework\DomDocument\DomDocumentFactory; + +class DomDocumentFactoryTest extends \PHPUnit\Framework\TestCase +{ + public function testCreateReturnsDomDocument() + { + $domDocumentFactory = new DomDocumentFactory(); + $this->assertInstanceOf( + \DOMDocument::class, + $domDocumentFactory->create() + ); + } +} diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index 17669bd78bd698c3f035091f5d180a4ed03146d2..61e36c908eeff3931473163f13c1da11d0c03919 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -20,6 +20,7 @@ "ext-openssl": "*", "lib-libxml": "*", "ext-xsl": "*", + "ext-bcmath": "*", "symfony/process": "~2.1", "colinmollenhour/php-redis-session-abstract": "1.3.4", "composer/composer": "1.4.1", @@ -33,7 +34,8 @@ "zendframework/zend-uri": "^2.5.1", "zendframework/zend-validator": "^2.6.0", "zendframework/zend-stdlib": "^2.7.7", - "zendframework/zend-http": "^2.6.0" + "zendframework/zend-http": "^2.6.0", + "magento/zendframework1": "~1.13.0" }, "suggest": { "ext-imagick": "Use Image Magick >=3.0.0 as an optional alternative image processing library" diff --git a/lib/web/i18n/en_US.csv b/lib/web/i18n/en_US.csv index 5c63a191420a4d2daeebffef9dcab9e886edbd93..4acc62aa6dc81edd1f2b4c6397d577bdd9f3621c 100644 --- a/lib/web/i18n/en_US.csv +++ b/lib/web/i18n/en_US.csv @@ -95,7 +95,7 @@ Submit,Submit "Please enter valid SKU key.","Please enter valid SKU key." "Please enter a valid number.","Please enter a valid number." "This is required field","This is required field" -"Admin is a required field in the each row.","Admin is a required field in the each row." +"Admin is a required field in each row.","Admin is a required field in each row." "Password cannot be the same as email address.","Password cannot be the same as email address." "Please fix this field.","Please fix this field." "Please enter a valid email address.","Please enter a valid email address." diff --git a/lib/web/mage/apply/main.js b/lib/web/mage/apply/main.js index 60b737e59e11089fd269f963720f8954ce3275ed..489e467f9b1100aa128d33fefe95324a75b7f36b 100644 --- a/lib/web/mage/apply/main.js +++ b/lib/web/mage/apply/main.js @@ -32,6 +32,12 @@ define([ } else if ($(el)[component]) { $(el)[component](config); } + }, function (error) { + if ('console' in window && typeof window.console.error === 'function') { + console.error(error); + } + + return true; }); } diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index e12e7c90d4c4152133e400947b69087a44a20f13..5f4137bb06db4238571b97246eb51ba78e283e0a 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -1552,15 +1552,15 @@ ], 'required-text-swatch-entry': [ tableSingleValidation, - $.mage.__('Admin is a required field in the each row.') + $.mage.__('Admin is a required field in each row.') ], 'required-visual-swatch-entry': [ tableSingleValidation, - $.mage.__('Admin is a required field in the each row.') + $.mage.__('Admin is a required field in each row.') ], 'required-dropdown-attribute-entry': [ tableSingleValidation, - $.mage.__('Admin is a required field in the each row.') + $.mage.__('Admin is a required field in each row.') ], 'validate-item-quantity': [ function (value, element, params) { diff --git a/lib/web/magnifier/magnifier.js b/lib/web/magnifier/magnifier.js index 958af0e96641bed61c90bb161170f4ffa935b26e..c47536436892235cb90eb7315d360970857ab080 100644 --- a/lib/web/magnifier/magnifier.js +++ b/lib/web/magnifier/magnifier.js @@ -588,7 +588,7 @@ _init($box, gOptions); }); - $(document).on('mousemove', onMousemove); + $box.on('mousemove', onMousemove); _init($box, gOptions); } diff --git a/nginx.conf.sample b/nginx.conf.sample index 7257c329df24b295905c7caad55c8257e679add5..1e20a51a511d3b9c95f3af13dc0c6236a556a897 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -100,7 +100,7 @@ location /static/ { # Remove signature of the static files that is used to overcome the browser cache location ~ ^/static/version { - rewrite ^/static/(version\d*/)?(.*)$ /static/$2 last; + rewrite ^/static/(version[^/]+/)?(.*)$ /static/$2 last; } location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ { diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 154915cd3a4fcb7b9abd2bf5cb304625a50470a3..ef407b9206c0f9ccc899b3ced8c8367fb99ac870 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -12976,6 +12976,9 @@ vars.put("admin_user", adminUser); //Index of the current product from the cluster Random random = new Random(); + if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); + } int iterator = random.nextInt(clusterLength); if (iterator == 0) { iterator = 1; @@ -32318,7 +32321,7 @@ vars.put("admin_user", adminUser); <stringProp name="RegexExtractor.regex">actions":\{"edit":\{"href":"(?:http|https):\\/\\/(.*?)\\/customer\\/index\\/edit\\/id\\/(\d+)\\/",</stringProp> <stringProp name="RegexExtractor.template">/customer/index/edit/id/$2$/</stringProp> <stringProp name="RegexExtractor.default"/> - <stringProp name="RegexExtractor.match_number">0</stringProp> + <stringProp name="RegexExtractor.match_number">1</stringProp> </RegexExtractor> <hashTree/> <ResponseAssertion guiclass="AssertionGui" testclass="ResponseAssertion" testname="Assert customer edit url" enabled="true"> @@ -34535,6 +34538,9 @@ vars.put("admin_user", adminUser); //Index of the current product from the cluster Random random = new Random(); + if (${seedForRandom} > 0) { + random.setSeed(${seedForRandom} + ${__threadNum}); + } int iterator = random.nextInt(clusterLength); if (iterator == 0) { iterator = 1; diff --git a/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php b/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php index c817d2e07660a35e478747f5156a0d21225089a8..d4f192255c2096768addd41678e0458792112302 100644 --- a/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php +++ b/setup/src/Magento/Setup/Console/Command/GenerateFixturesCommand.php @@ -96,8 +96,10 @@ class GenerateFixturesCommand extends Command $indexerRegistry = $fixtureModel->getObjectManager() ->create(\Magento\Framework\Indexer\IndexerRegistry::class); + $indexersState = []; foreach ($indexerListIds as $indexerId) { $indexer = $indexerRegistry->get($indexerId['indexer_id']); + $indexersState[$indexerId['indexer_id']] = $indexer->isScheduled(); $indexer->setScheduled(true); } @@ -107,6 +109,12 @@ class GenerateFixturesCommand extends Command $this->clearChangelog(); + foreach ($indexerListIds as $indexerId) { + /** @var $indexer \Magento\Indexer\Model\Indexer */ + $indexer = $indexerRegistry->get($indexerId['indexer_id']); + $indexer->setScheduled($indexersState[$indexerId['indexer_id']]); + } + /** @var \Magento\Setup\Fixtures\IndexersStatesApplyFixture $indexerFixture */ $indexerFixture = $fixtureModel ->getFixtureByName(\Magento\Setup\Fixtures\IndexersStatesApplyFixture::class); diff --git a/setup/view/magento/setup/readiness-check/progress.phtml b/setup/view/magento/setup/readiness-check/progress.phtml index c1ac41c6b5f2eb0fdeaeb3398656305fe0538e99..eb9dd0ce9d1aa3575b32d4950c3e973dabce93c0 100755 --- a/setup/view/magento/setup/readiness-check/progress.phtml +++ b/setup/view/magento/setup/readiness-check/progress.phtml @@ -336,7 +336,7 @@ </div> <p ng-show="componentDependency.expanded">For additional assistance, see - <a href="http://devdocs.magento.com/guides/v2.0/install-gde/trouble/php/tshoot_php-set.html" + <a href="http://devdocs.magento.com/guides/v2.2/install-gde/trouble/php/tshoot_php-set.html" target="_blank">PHP settings check help </a>. </p> @@ -392,7 +392,7 @@ <div class="readiness-check-side"> <p class="side-title">Need Help?</p> - <a href="http://devdocs.magento.com/guides/v2.0/install-gde/system-requirements.html" target="_blank">PHP Extension Help</a> + <a href="http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP Extension Help</a> </div> <span class="readiness-check-icon icon-failed-round"></span> @@ -413,7 +413,7 @@ <p> The best way to resolve this is to install the correct missing extensions. The exact fix depends on our server, your host, and other system variables. <br> - Our <a href="http://devdocs.magento.com/guides/v2.0/install-gde/system-requirements.html" target="_blank">PHP extension help</a> can get you started. + Our <a href="http://devdocs.magento.com/guides/v2.2/install-gde/system-requirements.html" target="_blank">PHP extension help</a> can get you started. </p> <p> For additional assistance, contact your hosting provider. @@ -477,7 +477,7 @@ <div class="readiness-check-side"> <p class="side-title">Need Help?</p> - <a href="http://devdocs.magento.com/guides/v2.0/install-gde/install/file-system-perms.html" target="_blank">File Permission Help</a> + <a href="http://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> </div> <span class="readiness-check-icon icon-failed-round"></span> @@ -500,7 +500,7 @@ The best way to resolve this is to allow write permissions for files in the following Magento directories and subdirectories. The exact fix depends on your server, your host, and other system variables. <br> - For help, see our <a href="http://devdocs.magento.com/guides/v2.0/install-gde/install/file-system-perms.html" target="_blank">File Permission Help</a> or call your hosting provider. + For help, see our <a href="http://devdocs.magento.com/guides/v2.2/install-gde/prereq/file-system-perms.html" target="_blank">File Permission Help</a> or call your hosting provider. </p> <ul class="list" ng-show="permissions.expanded" ng-init="showDetails=false"> <li