From eb9592220c8a39e66a326cae7f8d63e9355de117 Mon Sep 17 00:00:00 2001 From: Olga Lytvynenko Date: Mon, 5 Sep 2016 15:52:56 +0300 Subject: [PATCH 1/4] MAGETWO-57832: [Backport] - [Github] [UI] Wrong position of Swatches warning message on Checkout - for 2.1 --- .../view/frontend/web/js/swatch-renderer.js | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index dfd4b2bb2da0f..e34c8f277d18b 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -169,6 +169,9 @@ define([ //selector of product images gallery wrapper mediaGallerySelector: '[data-gallery-role=gallery-placeholder]', + // selector of category product tile wrapper + selectorProductTile: '.product-item', + // number of controls to show (false or zero = show all) numberToShow: false, @@ -191,7 +194,10 @@ define([ mediaGalleryInitial: [{}], // - onlyMainImg: false + onlyMainImg: false, + + // whether swatches are rendered in product list or on product page + inProductList: false }, /** @@ -246,7 +252,9 @@ define([ 'img': $main.find('.product-image-photo').attr('src') }]; } - this.productForm = this.element.parents(this.options.selectorProduct).find('form:first'); + + this.productForm = this.element.parents(this.options.selectorProductTile).find('form:first'); + this.inProductList = this.productForm.length > 0; }, /** @@ -280,7 +288,7 @@ define([ ''; } - if ($widget.productForm) { + if ($widget.inProductList) { $widget.productForm.append(input); input = ''; } @@ -498,13 +506,16 @@ define([ * @private */ _OnClick: function ($this, $widget) { - var $parent = $this.parents('.' + $widget.options.classes.attributeClass), $label = $parent.find('.' + $widget.options.classes.attributeSelectedOptionLabelClass), attributeId = $parent.attr('attribute-id'), + $input = $parent.find('.' + $widget.options.classes.attributeInput); + + if ($widget.inProductList) { $input = $widget.productForm.find( '.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]' ); + } if ($this.hasClass('disabled')) { return; @@ -543,9 +554,13 @@ define([ _OnChange: function ($this, $widget) { var $parent = $this.parents('.' + $widget.options.classes.attributeClass), attributeId = $parent.attr('attribute-id'), + $input = $parent.find('.' + $widget.options.classes.attributeInput); + + if ($widget.inProductList) { $input = $widget.productForm.find( '.' + $widget.options.classes.attributeInput + '[name="super_attribute[' + attributeId + ']"]' ); + } if ($this.val() > 0) { $parent.attr('option-selected', $this.val()); @@ -687,7 +702,6 @@ define([ 'prices': $widget._getPrices(result, $productPrice.priceBox('option').prices) } ); - }, /** From c97ba46b14a9de5aa679d04e73311694184dd634 Mon Sep 17 00:00:00 2001 From: Vladyslav Shcherbyna Date: Wed, 26 Oct 2016 13:04:11 +0300 Subject: [PATCH 2/4] MAGETWO-58559: [Backport] - [Flat] Filter Exception - for 2.1 --- .../Indexer/Product/Flat/FlatTableBuilder.php | 10 +- .../Indexer/Product/Flat/Table/Builder.php | 63 ++++++ .../Product/Flat/Table/BuilderInterface.php | 40 ++++ .../Indexer/Product/Flat/TableBuilder.php | 57 +++-- .../Product/Flat/FlatTableBuilderTest.php | 204 ++++++++++++++++++ .../Product/Flat/Table/BuilderTest.php | 48 +++++ app/code/Magento/Catalog/etc/di.xml | 1 + 7 files changed, 406 insertions(+), 17 deletions(-) create mode 100644 app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php create mode 100644 app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php create mode 100644 app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php index c302ed823f898..148458b023555 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php @@ -221,7 +221,11 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS unset($tables[$entityTableName]); - $allColumns = array_merge(['entity_id', 'type_id', 'attribute_set_id'], $columnsList); + $allColumns = array_values( + array_unique( + array_merge(['entity_id', $linkField, 'type_id', 'attribute_set_id'], $columnsList) + ) + ); /* @var $status \Magento\Eav\Model\Entity\Attribute */ $status = $this->_productIndexerHelper->getAttribute('status'); @@ -263,7 +267,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS $select->joinLeft( $temporaryTableName, - "e.entity_id = " . $temporaryTableName . ".entity_id", + "e.${linkField} = ${temporaryTableName}.${linkField}", $columnsNames ); $allColumns = array_merge($allColumns, $columnsNames); @@ -277,7 +281,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS if (!empty($columnValueNames)) { $select->joinLeft( $temporaryValueTableName, - "e.${linkField} = " . $temporaryValueTableName . ".entity_id", + "e.${linkField} = " . $temporaryValueTableName . ".${linkField}", $columnValueNames ); $allColumns = array_merge($allColumns, $columnValueNames); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php new file mode 100644 index 0000000000000..b78266c5447b4 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php @@ -0,0 +1,63 @@ +tableInstance = $connection->newTable($tableName); + } + + /** + * Adds column to table. + * + * $options contains additional options for columns. Supported values are: + * - 'unsigned', for number types only. Default: FALSE. + * - 'precision', for numeric and decimal only. Default: taken from $size, if not set there then 0. + * - 'scale', for numeric and decimal only. Default: taken from $size, if not set there then 10. + * - 'default'. Default: not set. + * - 'nullable'. Default: TRUE. + * - 'primary', add column to primary index. Default: do not add. + * - 'primary_position', only for column in primary index. Default: count of primary columns + 1. + * - 'identity' or 'auto_increment'. Default: FALSE. + * + * @param string $name the column name + * @param string $type the column data type + * @param string|int|array $size the column length + * @param array $options array of additional options + * @param string $comment column description + * @return $this + * @throws \Zend_Db_Exception + */ + public function addColumn($name, $type, $size = null, $options = [], $comment = null) + { + $this->tableInstance->addColumn($name, $type, $size, $options, $comment); + return $this; + } + + /** + * @return \Magento\Framework\DB\Ddl\Table + */ + public function getTable() + { + return $this->tableInstance; + } +} diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php new file mode 100644 index 0000000000000..b24db9ee74334 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/BuilderInterface.php @@ -0,0 +1,40 @@ +_connection->newTable($tableName); - $valueTemporaryTable = $this->_connection->newTable($valueTableName); + $temporaryTableBuilder = $this->getTableBuilderFactory()->create( + [ + 'connection' => $this->_connection, + 'tableName' => $tableName + ] + ); + $valueTemporaryTableBuilder = $this->getTableBuilderFactory()->create( + [ + 'connection' => $this->_connection, + 'tableName' => $valueTableName + ] + ); $flatColumns = $this->_productIndexerHelper->getFlatColumns(); - $temporaryTable->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $temporaryTableBuilder->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); - $temporaryTable->addColumn('type_id', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT); + $temporaryTableBuilder->addColumn('type_id', \Magento\Framework\DB\Ddl\Table::TYPE_TEXT); - $temporaryTable->addColumn('attribute_set_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $temporaryTableBuilder->addColumn('attribute_set_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); - $valueTemporaryTable->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); + $valueTemporaryTableBuilder->addColumn('entity_id', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER); /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ foreach ($columns as $columnName => $attribute) { @@ -145,7 +160,7 @@ protected function _createTemporaryTable($tableName, array $columns, $valueField $column = $column[$attributeCode]; } - $temporaryTable->addColumn( + $temporaryTableBuilder->addColumn( $columnName, $column['type'], isset($column['length']) ? $column['length'] : null @@ -154,7 +169,7 @@ protected function _createTemporaryTable($tableName, array $columns, $valueField $columnValueName = $attributeCode . $valueFieldSuffix; if (isset($flatColumns[$columnValueName])) { $columnValue = $flatColumns[$columnValueName]; - $valueTemporaryTable->addColumn( + $valueTemporaryTableBuilder->addColumn( $columnValueName, $columnValue['type'], isset($columnValue['length']) ? $columnValue['length'] : null @@ -162,11 +177,11 @@ protected function _createTemporaryTable($tableName, array $columns, $valueField } } $this->_connection->dropTemporaryTable($tableName); - $this->_connection->createTemporaryTable($temporaryTable); + $this->_connection->createTemporaryTable($temporaryTableBuilder->getTable()); - if (count($valueTemporaryTable->getColumns()) > 1) { + if (count($valueTemporaryTableBuilder->getTable()->getColumns()) > 1) { $this->_connection->dropTemporaryTable($valueTableName); - $this->_connection->createTemporaryTable($valueTemporaryTable); + $this->_connection->createTemporaryTable($valueTemporaryTableBuilder->getTable()); $valueTables[$valueTableName] = $valueTableName; } } @@ -197,7 +212,8 @@ protected function _fillTemporaryEntityTable($tableName, array $columns, array $ if (!empty($columns)) { $select = $this->_connection->select(); $temporaryEntityTable = $this->_getTemporaryTableName($tableName); - $idsColumns = ['entity_id', 'type_id', 'attribute_set_id']; + $metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class); + $idsColumns = array_unique([$metadata->getLinkField(), 'entity_id', 'type_id', 'attribute_set_id']); $columns = array_merge($idsColumns, array_keys($columns)); @@ -261,7 +277,7 @@ protected function _fillTemporaryTable( ); $temporaryTableName = $this->_getTemporaryTableName($tableName); $temporaryValueTableName = $temporaryTableName . $valueFieldSuffix; - $keyColumn = ['entity_id']; + $keyColumn = array_unique([$metadata->getLinkField(), 'entity_id']); $columns = array_merge($keyColumn, array_keys($columnsList)); $valueColumns = $keyColumn; $flatColumns = $this->_productIndexerHelper->getFlatColumns(); @@ -333,6 +349,19 @@ protected function _fillTemporaryTable( } } + /** + * @return BuilderInterfaceFactory + */ + private function getTableBuilderFactory() + { + if (null === $this->tableBuilderFactory) { + $this->tableBuilderFactory = \Magento\Framework\App\ObjectManager::getInstance() + ->get(BuilderInterfaceFactory::class); + } + + return $this->tableBuilderFactory; + } + /** * @return \Magento\Framework\EntityManager\MetadataPool */ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php new file mode 100644 index 0000000000000..c57dd950dc3e0 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/FlatTableBuilderTest.php @@ -0,0 +1,204 @@ +flatIndexerMock = $this->getMockBuilder(\Magento\Catalog\Helper\Product\Flat\Indexer::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->tableDataMock = $this->getMockBuilder( + \Magento\Catalog\Model\Indexer\Product\Flat\TableDataInterface::class + )->disableOriginalConstructor()->getMockForAbstractClass(); + $this->connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->metadataPoolMock = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) + ->disableOriginalConstructor() + ->getMock(); + $this->metadataMock = $this->getMockBuilder( + \Magento\Framework\EntityManager\EntityMetadataInterface::class + )->disableOriginalConstructor()->getMockForAbstractClass(); + $this->metadataMock->expects($this->any())->method('getLinkField')->willReturn('entity_id'); + + $this->flatTableBuilder = $objectManagerHelper->getObject( + \Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder::class, + [ + 'productIndexerHelper' => $this->flatIndexerMock, + 'resource' => $this->resourceMock, + 'config' => $this->scopeConfigMock, + 'storeManager' => $this->storeManagerMock, + 'tableData' => $this->tableDataMock, + '_connection' => $this->connectionMock + ] + ); + $objectManagerHelper->setBackwardCompatibleProperty( + $this->flatTableBuilder, + 'metadataPool', + $this->metadataPoolMock + ); + } + + public function testBuild() + { + list($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables) = [1, [], '', '', true]; + $tableName = 'catalog_product_entity'; + $attributeTable = 'catalog_product_entity_int'; + $temporaryTableName = 'catalog_product_entity_int_tmp_indexer'; + $temporaryValueTableName = 'catalog_product_entity_int_tmp_indexer'; + $linkField = 'entity_id'; + $statusId = 22; + $this->flatIndexerMock->expects($this->once())->method('getAttributes')->willReturn([]); + $this->flatIndexerMock->expects($this->exactly(3))->method('getFlatColumns') + ->willReturnOnConsecutiveCalls( + [], + [$linkField => []], + [$linkField => []] + ); + $this->flatIndexerMock->expects($this->once())->method('getFlatIndexes')->willReturn([]); + $statusAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute::class) + ->disableOriginalConstructor() + ->getMock(); + $this->flatIndexerMock->expects($this->once())->method('getTablesStructure') + ->willReturn( + [ + 'catalog_product_entity' => [ + $linkField => $statusAttributeMock + ], + 'catalog_product_entity_int' => [ + $linkField => $statusAttributeMock + ] + ] + ); + $this->flatIndexerMock->expects($this->atLeastOnce())->method('getTable') + ->withConsecutive( + [$tableName], + ['catalog_product_website'] + ) + ->willReturn( + $tableName, + 'catalog_product_website' + ); + $this->flatIndexerMock->expects($this->once())->method('getAttribute') + ->with('status') + ->willReturn($statusAttributeMock); + $backendMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend::class) + ->disableOriginalConstructor() + ->getMock(); + $backendMock->expects($this->atLeastOnce())->method('getTable')->willReturn($attributeTable); + $statusAttributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn( + $backendMock + ); + $statusAttributeMock->expects($this->atLeastOnce())->method('getId')->willReturn($statusId); + $tableMock = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionMock->expects($this->any())->method('newTable')->willReturn($tableMock); + $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($selectMock); + $selectMock->expects($this->once())->method('from')->with( + ['et' => 'catalog_product_entity_tmp_indexer'], + [$linkField, 'type_id', 'attribute_set_id'] + )->willReturnSelf(); + $selectMock->expects($this->atLeastOnce())->method('joinInner')->willReturnSelf(); + $selectMock->expects($this->exactly(3))->method('joinLeft') + ->withConsecutive( + [ + ['dstatus' => $attributeTable], + sprintf( + 'e.%s = dstatus.%s AND dstatus.store_id = %s AND dstatus.attribute_id = %s', + $linkField, + $linkField, + $storeId, + $statusId + ), + [] + ], + [ + $temporaryTableName, + "e.{$linkField} = ${temporaryTableName}.{$linkField}", + [$linkField] + ], + [ + $temporaryValueTableName, + "e.${linkField} = " . $temporaryValueTableName . ".${linkField}", + [$linkField] + ] + )->willReturnSelf(); + $this->metadataPoolMock->expects($this->atLeastOnce())->method('getMetadata')->with(ProductInterface::class) + ->willReturn($this->metadataMock); + $storeMock = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->storeManagerMock->expects($this->once())->method('getStore')->with($storeId)->willReturn($storeMock); + $this->flatTableBuilder->build($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix, $fillTmpTables); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php new file mode 100644 index 0000000000000..4296b28c21689 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php @@ -0,0 +1,48 @@ +connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $table = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class) + ->disableOriginalConstructor() + ->getMock(); + $table->expects($this->once())->method('addColumn') + ->with('test', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER) + ->willReturnSelf(); + $tableName = 'test_table'; + $this->connectionMock->expects($this->once()) + ->method('newTable') + ->with($tableName) + ->willReturn($table); + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + /** + * @var $builder \Magento\Catalog\Model\Indexer\Product\Flat\Table\Builder + */ + $builder = $objectManagerHelper->getObject( + \Magento\Catalog\Model\Indexer\Product\Flat\Table\Builder::class, + [ + 'connection' => $this->connectionMock, + 'tableName' => $tableName + ] + ); + $this->assertEquals($builder, $builder->addColumn('test', \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER)); + } +} + diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index 7e02c1def6dd4..8e4652cdc59cf 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -16,6 +16,7 @@ + From b565c0c7f9253b251a85eef62eae4ef40b00d831 Mon Sep 17 00:00:00 2001 From: Vladyslav Shcherbyna Date: Wed, 26 Oct 2016 14:43:47 +0300 Subject: [PATCH 3/4] MAGETWO-58559: [Backport] - [Flat] Filter Exception - for 2.1 --- .../Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php index 4296b28c21689..915c66c873744 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Table/BuilderTest.php @@ -3,7 +3,7 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ -namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Flat\Table\Builder; +namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Flat\Table; /** * Class BuilderTest From 95cbfafe603219bd24032bb44c2c6e07dcb2efe1 Mon Sep 17 00:00:00 2001 From: Oleksandr Miroshnichenko Date: Mon, 3 Oct 2016 17:29:25 +0300 Subject: [PATCH 4/4] MAGETWO-59416: [Backport] - [Github] Admin can't reset password for more than one customer #5260 - for 2.1 --- .../Adminhtml/Index/ResetPassword.php | 3 + .../Adminhtml/Index/ResetPasswordTest.php | 52 ++++++++++- .../Model/Plugin/AccountManagement.php | 12 ++- .../Magento/Security/etc/adminhtml/di.xml | 5 ++ .../Adminhtml/Index/ResetPasswordTest.php | 88 +++++++++++++++++++ 5 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php index 45d31dc0e2764..3c3fa73087e4a 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/ResetPassword.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Controller\Adminhtml\Index; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\SecurityViolationException; class ResetPassword extends \Magento\Customer\Controller\Adminhtml\Index { @@ -40,6 +41,8 @@ public function execute() $messages = $exception->getMessage(); } $this->_addSessionErrorMessages($messages); + } catch (SecurityViolationException $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); } catch (\Exception $exception) { $this->messageManager->addException( $exception, diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php index 3cded12552b34..3b3791da7e7a8 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/ResetPasswordTest.php @@ -141,7 +141,7 @@ protected function setUp() $this->messageManager = $this->getMockBuilder( 'Magento\Framework\Message\Manager' )->disableOriginalConstructor()->setMethods( - ['addSuccess', 'addMessage', 'addException'] + ['addSuccess', 'addMessage', 'addException', 'addErrorMessage'] )->getMock(); $this->resultRedirectFactoryMock = $this->getMockBuilder('Magento\Backend\Model\View\Result\RedirectFactory') @@ -332,6 +332,56 @@ public function testResetPasswordActionCoreException() $this->_testedObject->execute(); } + public function testResetPasswordActionSecurityException() + { + $securityText = 'Security violation.'; + $exception = new \Magento\Framework\Exception\SecurityViolationException(__($securityText)); + $customerId = 1; + $email = 'some@example.com'; + $websiteId = 1; + + $this->_request->expects( + $this->once() + )->method( + 'getParam' + )->with( + $this->equalTo('customer_id'), + $this->equalTo(0) + )->will( + $this->returnValue($customerId) + ); + $customer = $this->getMockForAbstractClass( + \Magento\Customer\Api\Data\CustomerInterface::class, + ['getId', 'getEmail', 'getWebsiteId'] + ); + $customer->expects($this->once())->method('getEmail')->will($this->returnValue($email)); + $customer->expects($this->once())->method('getWebsiteId')->will($this->returnValue($websiteId)); + $this->_customerRepositoryMock->expects( + $this->once() + )->method( + 'getById' + )->with( + $customerId + )->will( + $this->returnValue($customer) + ); + $this->_customerAccountManagementMock->expects( + $this->once() + )->method( + 'initiatePasswordReset' + )->willThrowException($exception); + + $this->messageManager->expects( + $this->once() + )->method( + 'addErrorMessage' + )->with( + $this->equalTo($exception->getMessage()) + ); + + $this->_testedObject->execute(); + } + public function testResetPasswordActionCoreExceptionWarn() { $warningText = 'Warning'; diff --git a/app/code/Magento/Security/Model/Plugin/AccountManagement.php b/app/code/Magento/Security/Model/Plugin/AccountManagement.php index ba1d4af5618b7..c65442ec40020 100644 --- a/app/code/Magento/Security/Model/Plugin/AccountManagement.php +++ b/app/code/Magento/Security/Model/Plugin/AccountManagement.php @@ -25,18 +25,26 @@ class AccountManagement */ protected $securityManager; + /** + * @var int + */ + protected $passwordRequestEvent; + /** * AccountManagement constructor. * * @param \Magento\Framework\App\RequestInterface $request * @param SecurityManager $securityManager + * @param int $passwordRequestEvent */ public function __construct( \Magento\Framework\App\RequestInterface $request, - \Magento\Security\Model\SecurityManager $securityManager + \Magento\Security\Model\SecurityManager $securityManager, + $passwordRequestEvent = PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST ) { $this->request = $request; $this->securityManager = $securityManager; + $this->passwordRequestEvent = $passwordRequestEvent; } /** @@ -56,7 +64,7 @@ public function beforeInitiatePasswordReset( $websiteId = null ) { $this->securityManager->performSecurityCheck( - PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST, + $this->passwordRequestEvent, $email ); return [$email, $template, $websiteId]; diff --git a/app/code/Magento/Security/etc/adminhtml/di.xml b/app/code/Magento/Security/etc/adminhtml/di.xml index 4cbd3c3adc567..c134638d1266e 100644 --- a/app/code/Magento/Security/etc/adminhtml/di.xml +++ b/app/code/Magento/Security/etc/adminhtml/di.xml @@ -15,6 +15,11 @@ + + + Magento\Security\Model\PasswordResetRequestEvent::ADMIN_PASSWORD_RESET_REQUEST + + diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php new file mode 100644 index 0000000000000..f3571c17ddffe --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/ResetPasswordTest.php @@ -0,0 +1,88 @@ +passwordResetRequestEventCreate( + \Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST + ); + $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->dispatch('backend/customer/index/resetPassword'); + $this->assertSessionMessages( + $this->equalTo(['The customer will receive an email with a link to reset password.']), + \Magento\Framework\Message\MessageInterface::TYPE_SUCCESS + ); + $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl . 'edit')); + } + + /** + * Checks reset password functionality with default settings, customer and admin reset request events. + * + * @magentoConfigFixture current_store admin/security/limit_password_reset_requests_method 1 + * @magentoConfigFixture current_store admin/security/min_time_between_password_reset_requests 10 + * @magentoConfigFixture current_store contact/email/recipient_email hello@example.com + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testResetPasswordWithSecurityViolationException() + { + $this->passwordResetRequestEventCreate( + \Magento\Security\Model\PasswordResetRequestEvent::CUSTOMER_PASSWORD_RESET_REQUEST + ); + $this->passwordResetRequestEventCreate( + \Magento\Security\Model\PasswordResetRequestEvent::ADMIN_PASSWORD_RESET_REQUEST + ); + $this->getRequest()->setPostValue(['customer_id' => '1']); + $this->dispatch('backend/customer/index/resetPassword'); + $this->assertSessionMessages( + $this->equalTo( + ['Too many password reset requests. Please wait and try again or contact hello@example.com.'] + ), + \Magento\Framework\Message\MessageInterface::TYPE_ERROR + ); + $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl . 'edit')); + } + + /** + * Create and save reset request event with provided request type. + * + * @param int $requestType + */ + private function passwordResetRequestEventCreate($requestType) + { + $passwordResetRequestEventFactory = $this->_objectManager->get( + \Magento\Security\Model\PasswordResetRequestEventFactory::class + ); + $passwordResetRequestEvent = $passwordResetRequestEventFactory->create(); + $passwordResetRequestEvent + ->setRequestType($requestType) + ->setAccountReference('customer@example.com') + ->setCreatedAt(strtotime('now')) + ->setIp('3232249856') + ->save(); + } +}