diff --git a/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml b/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml index b50183ced29b4..af369800287c1 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml @@ -42,7 +42,7 @@ <% if (data.items.length) { %> <% _.each(data.items, function(value){ %>
  • + <%= data.optionData(value) %> > <%- value.name %> <%- value.type %> diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php index 3a1611299288c..466ba746fa108 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php @@ -56,39 +56,36 @@ public function __construct( * @return \Magento\Catalog\Model\Indexer\Product\Flat * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function write($storeId, $productId, $valueFieldSuffix = '') { $flatTable = $this->_productIndexerHelper->getFlatTableName($storeId); + $entityTableName = $this->_productIndexerHelper->getTable('catalog_product_entity'); $attributes = $this->_productIndexerHelper->getAttributes(); $eavAttributes = $this->_productIndexerHelper->getTablesStructure($attributes); $updateData = []; $describe = $this->_connection->describeTable($flatTable); + $metadata = $this->getMetadataPool()->getMetadata(ProductInterface::class); + $linkField = $metadata->getLinkField(); foreach ($eavAttributes as $tableName => $tableColumns) { $columnsChunks = array_chunk($tableColumns, self::ATTRIBUTES_CHUNK_SIZE, true); foreach ($columnsChunks as $columns) { $select = $this->_connection->select(); - $selectValue = $this->_connection->select(); - $keyColumns = [ - 'entity_id' => 'e.entity_id', - 'attribute_id' => 't.attribute_id', - 'value' => $this->_connection->getIfNullSql('`t2`.`value`', '`t`.`value`'), - ]; - - if ($tableName != $this->_productIndexerHelper->getTable('catalog_product_entity')) { + + if ($tableName != $entityTableName) { $valueColumns = []; $ids = []; $select->from( - ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')], - $keyColumns - ); - - $selectValue->from( - ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')], - $keyColumns + ['e' => $entityTableName], + [ + 'entity_id' => 'e.entity_id', + 'attribute_id' => 't.attribute_id', + 'value' => $this->_connection->getIfNullSql('`t2`.`value`', '`t`.`value`'), + ] ); /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ @@ -97,8 +94,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '') $ids[$attribute->getId()] = $columnName; } } - $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); - $select->joinLeft( + $select->joinInner( ['t' => $tableName], sprintf('e.%s = t.%s ', $linkField, $linkField) . $this->_connection->quoteInto( ' AND t.attribute_id IN (?)', @@ -116,8 +112,6 @@ public function write($storeId, $productId, $valueFieldSuffix = '') [] )->where( 'e.entity_id = ' . $productId - )->where( - 't.attribute_id IS NOT NULL' ); $cursor = $this->_connection->query($select); while ($row = $cursor->fetch(\Zend_Db::FETCH_ASSOC)) { @@ -157,7 +151,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '') $columnNames[] = 'attribute_set_id'; $columnNames[] = 'type_id'; $select->from( - ['e' => $this->_productIndexerHelper->getTable('catalog_product_entity')], + ['e' => $entityTableName], $columnNames )->where( 'e.entity_id = ' . $productId @@ -175,7 +169,9 @@ public function write($storeId, $productId, $valueFieldSuffix = '') if (!empty($updateData)) { $updateData += ['entity_id' => $productId]; - $updateData += ['row_id' => $productId]; + if ($linkField !== $metadata->getIdentifierField()) { + $updateData += [$linkField => $productId]; + } $updateFields = []; foreach ($updateData as $key => $value) { $updateFields[$key] = $key; @@ -187,6 +183,8 @@ public function write($storeId, $productId, $valueFieldSuffix = '') } /** + * Get MetadataPool instance + * * @return \Magento\Framework\EntityManager\MetadataPool */ private function getMetadataPool() diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php index 709f27d031ebe..6d0727259d9db 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Row.php @@ -5,16 +5,20 @@ */ namespace Magento\Catalog\Model\Indexer\Product\Flat\Action; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder; use Magento\Catalog\Model\Indexer\Product\Flat\TableBuilder; +use Magento\Framework\EntityManager\MetadataPool; /** - * Class Row reindex action + * Class Row reindex action. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction { /** - * @var \Magento\Catalog\Model\Indexer\Product\Flat\Action\Indexer + * @var Indexer */ protected $flatItemWriter; @@ -23,6 +27,11 @@ class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction */ protected $flatItemEraser; + /** + * @var MetadataPool + */ + private $metadataPool; + /** * @param \Magento\Framework\App\ResourceConnection $resource * @param \Magento\Store\Model\StoreManagerInterface $storeManager @@ -32,6 +41,7 @@ class Row extends \Magento\Catalog\Model\Indexer\Product\Flat\AbstractAction * @param FlatTableBuilder $flatTableBuilder * @param Indexer $flatItemWriter * @param Eraser $flatItemEraser + * @param MetadataPool|null $metadataPool */ public function __construct( \Magento\Framework\App\ResourceConnection $resource, @@ -41,7 +51,8 @@ public function __construct( TableBuilder $tableBuilder, FlatTableBuilder $flatTableBuilder, Indexer $flatItemWriter, - Eraser $flatItemEraser + Eraser $flatItemEraser, + MetadataPool $metadataPool = null ) { parent::__construct( $resource, @@ -53,6 +64,8 @@ public function __construct( ); $this->flatItemWriter = $flatItemWriter; $this->flatItemEraser = $flatItemEraser; + $this->metadataPool = $metadataPool ?: + \Magento\Framework\App\ObjectManager::getInstance()->get(MetadataPool::class); } /** @@ -61,7 +74,6 @@ public function __construct( * @param int|null $id * @return \Magento\Catalog\Model\Indexer\Product\Flat\Action\Row * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Zend_Db_Statement_Exception */ public function execute($id = null) { @@ -71,50 +83,47 @@ public function execute($id = null) ); } $ids = [$id]; - foreach ($this->_storeManager->getStores() as $store) { + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + + $stores = $this->_storeManager->getStores(); + foreach ($stores as $store) { $tableExists = $this->_isFlatTableExists($store->getId()); if ($tableExists) { $this->flatItemEraser->removeDeletedProducts($ids, $store->getId()); } /* @var $status \Magento\Eav\Model\Entity\Attribute */ - $status = $this->_productIndexerHelper->getAttribute('status'); + $status = $this->_productIndexerHelper->getAttribute(ProductInterface::STATUS); $statusTable = $status->getBackend()->getTable(); $statusConditions = [ 'store_id IN(0,' . (int)$store->getId() . ')', 'attribute_id = ' . (int)$status->getId(), - 'entity_id = ' . (int)$id + $linkField . ' = ' . (int)$id, ]; $select = $this->_connection->select(); - $select->from( - $statusTable, - ['value'] - )->where( - implode(' AND ', $statusConditions) - )->order( - 'store_id DESC' - ); + $select->from($statusTable, ['value']) + ->where(implode(' AND ', $statusConditions)) + ->order('store_id DESC') + ->limit(1); $result = $this->_connection->query($select); - $status = $result->fetch(1); + $status = $result->fetchColumn(0); - if ($status['value'] == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) { - if (isset($ids[0])) { - if (!$tableExists) { - $this->_flatTableBuilder->build( - $store->getId(), - [$ids[0]], - $this->_valueFieldSuffix, - $this->_tableDropSuffix, - false - ); - } - $this->flatItemWriter->write($store->getId(), $ids[0], $this->_valueFieldSuffix); + if ($status == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) { + if (!$tableExists) { + $this->_flatTableBuilder->build( + $store->getId(), + $ids, + $this->_valueFieldSuffix, + $this->_tableDropSuffix, + false + ); } - } - if ($status['value'] == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED) { + $this->flatItemWriter->write($store->getId(), $id, $this->_valueFieldSuffix); + } else { $this->flatItemEraser->deleteProductsFromStore($id, $store->getId()); } } + return $this; } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php index a49fa9a15e341..85d8dd8a14917 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php @@ -6,7 +6,9 @@ namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Flat\Action; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -19,45 +21,48 @@ class RowTest extends \PHPUnit\Framework\TestCase protected $model; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $storeManager; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $store; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $productIndexerHelper; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $resource; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $connection; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $flatItemWriter; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $flatItemEraser; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var MockObject */ protected $flatTableBuilder; + /** + * @inheritdoc + */ protected function setUp() { $objectManager = new ObjectManager($this); @@ -68,11 +73,11 @@ protected function setUp() $this->resource = $this->createMock(\Magento\Framework\App\ResourceConnection::class); $this->resource->expects($this->any())->method('getConnection') ->with('default') - ->will($this->returnValue($this->connection)); + ->willReturn($this->connection); $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); $this->store = $this->createMock(\Magento\Store\Model\Store::class); - $this->store->expects($this->any())->method('getId')->will($this->returnValue('store_id_1')); - $this->storeManager->expects($this->any())->method('getStores')->will($this->returnValue([$this->store])); + $this->store->expects($this->any())->method('getId')->willReturn('store_id_1'); + $this->storeManager->expects($this->any())->method('getStores')->willReturn([$this->store]); $this->flatItemEraser = $this->createMock(\Magento\Catalog\Model\Indexer\Product\Flat\Action\Eraser::class); $this->flatItemWriter = $this->createMock(\Magento\Catalog\Model\Indexer\Product\Flat\Action\Indexer::class); $this->flatTableBuilder = $this->createMock( @@ -89,9 +94,7 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); $backendMock->expects($this->any())->method('getTable')->willReturn($attributeTable); - $statusAttributeMock->expects($this->any())->method('getBackend')->willReturn( - $backendMock - ); + $statusAttributeMock->expects($this->any())->method('getBackend')->willReturn($backendMock); $statusAttributeMock->expects($this->any())->method('getId')->willReturn($statusId); $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->disableOriginalConstructor() @@ -102,24 +105,32 @@ protected function setUp() ['value'] )->willReturnSelf(); $selectMock->expects($this->any())->method('where')->willReturnSelf(); + $selectMock->expects($this->any())->method('order')->willReturnSelf(); + $selectMock->expects($this->any())->method('limit')->willReturnSelf(); $pdoMock = $this->createMock(\Zend_Db_Statement_Pdo::class); - $this->connection->expects($this->any()) - ->method('query') - ->with($selectMock) - ->will($this->returnValue($pdoMock)); - $pdoMock->expects($this->any())->method('fetch')->will($this->returnValue(['value' => 1])); + $this->connection->expects($this->any())->method('query')->with($selectMock)->willReturn($pdoMock); + $pdoMock->expects($this->any())->method('fetchColumn')->willReturn('1'); + + $metadataPool = $this->createMock(\Magento\Framework\EntityManager\MetadataPool::class); + $productMetadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->getMockForAbstractClass(); + $metadataPool->expects($this->any())->method('getMetadata')->with(ProductInterface::class) + ->willReturn($productMetadata); + $productMetadata->expects($this->any())->method('getLinkField')->willReturn('entity_id'); $this->model = $objectManager->getObject( \Magento\Catalog\Model\Indexer\Product\Flat\Action\Row::class, [ - 'resource' => $this->resource, - 'storeManager' => $this->storeManager, - 'productHelper' => $this->productIndexerHelper, - 'flatItemEraser' => $this->flatItemEraser, - 'flatItemWriter' => $this->flatItemWriter, - 'flatTableBuilder' => $this->flatTableBuilder + 'resource' => $this->resource, + 'storeManager' => $this->storeManager, + 'productHelper' => $this->productIndexerHelper, + 'flatItemEraser' => $this->flatItemEraser, + 'flatItemWriter' => $this->flatItemWriter, + 'flatTableBuilder' => $this->flatTableBuilder, ] ); + + $objectManager->setBackwardCompatibleProperty($this->model, 'metadataPool', $metadataPool); } /** @@ -134,9 +145,9 @@ public function testExecuteWithEmptyId() public function testExecuteWithNonExistingFlatTablesCreatesTables() { $this->productIndexerHelper->expects($this->any())->method('getFlatTableName') - ->will($this->returnValue('store_flat_table')); + ->willReturn('store_flat_table'); $this->connection->expects($this->any())->method('isTableExists')->with('store_flat_table') - ->will($this->returnValue(false)); + ->willReturn(false); $this->flatItemEraser->expects($this->never())->method('removeDeletedProducts'); $this->flatTableBuilder->expects($this->once())->method('build')->with('store_id_1', ['product_id_1']); $this->flatItemWriter->expects($this->once())->method('write')->with('store_id_1', 'product_id_1'); @@ -146,9 +157,9 @@ public function testExecuteWithNonExistingFlatTablesCreatesTables() public function testExecuteWithExistingFlatTablesCreatesTables() { $this->productIndexerHelper->expects($this->any())->method('getFlatTableName') - ->will($this->returnValue('store_flat_table')); + ->willReturn('store_flat_table'); $this->connection->expects($this->any())->method('isTableExists')->with('store_flat_table') - ->will($this->returnValue(true)); + ->willReturn(true); $this->flatItemEraser->expects($this->once())->method('removeDeletedProducts'); $this->flatTableBuilder->expects($this->never())->method('build')->with('store_id_1', ['product_id_1']); $this->model->execute('product_id_1'); diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml index 6a5f6c4648494..a7e8564e7a1d8 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml @@ -14,7 +14,7 @@ <% } %> <% if (!data.term && data.items.length && !data.allShown()) { %> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml index 716a363ec5d78..741da96179b8c 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml @@ -16,9 +16,6 @@ - - - diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml index 072385c46645a..befe0b0ce7f98 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest.xml @@ -17,9 +17,6 @@ - - - @@ -77,9 +74,6 @@ - - - @@ -103,9 +97,6 @@ - - - @@ -129,9 +120,6 @@ - - - @@ -155,9 +143,6 @@ - - - diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml index 83770ffff5eab..d3546d06492be 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml @@ -17,9 +17,6 @@ - - - diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml index 55f775e40feaa..e7be6e8443a36 100644 --- a/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml +++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/StorefrontInactiveCatalogRuleTest.xml @@ -17,9 +17,6 @@ - - - diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/cart/shipping-estimation.js b/app/code/Magento/Checkout/view/frontend/web/js/view/cart/shipping-estimation.js index 39b5ea0299ab8..a857d89a72b14 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/cart/shipping-estimation.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/cart/shipping-estimation.js @@ -55,6 +55,12 @@ define( checkoutDataResolver.resolveEstimationAddress(); address = quote.isVirtual() ? quote.billingAddress() : quote.shippingAddress(); + if (!address && quote.isVirtual()) { + address = addressConverter.formAddressDataToQuoteAddress( + checkoutData.getSelectedBillingAddress() + ); + } + if (address) { estimatedAddress = address.isEditable() ? addressConverter.quoteAddressToFormAddressData(address) : diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml index 253a6a83c9438..e8b11b27ddc70 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml @@ -9,6 +9,7 @@
    + diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml index 53b07b2b6a51c..81612d1c64334 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml @@ -13,5 +13,6 @@ +
    diff --git a/app/code/Magento/Email/view/frontend/web/logo_email.png b/app/code/Magento/Email/view/frontend/web/logo_email.png index 0cca183e08da2..215e9d06edcdc 100644 Binary files a/app/code/Magento/Email/view/frontend/web/logo_email.png and b/app/code/Magento/Email/view/frontend/web/logo_email.png differ diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml new file mode 100644 index 0000000000000..fe2fc49f1d015 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml new file mode 100644 index 0000000000000..4e89e5476c3bc --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontSalesOrderPrintPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontSalesOrderPrintPage.xml new file mode 100644 index 0000000000000..874e6889ec58c --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontSalesOrderPrintPage.xml @@ -0,0 +1,15 @@ + + + + + +
    + + diff --git a/app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml new file mode 100644 index 0000000000000..b08a66140fabf --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml @@ -0,0 +1,14 @@ + + + + +
    + +
    +
    diff --git a/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml new file mode 100644 index 0000000000000..ec725ad5fe4c5 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml @@ -0,0 +1,78 @@ + + + + + + + + + <description + value="Check while order printing URL with an id of not relevant order redirects to order history"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-92854"/> + <group value="sales"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <createData entity="Simple_US_Customer" stepKey="createCustomer2"/> + </before> + <after> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCustomer2" stepKey="deleteCustomer2"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + </after> + + <!--Log in to Storefront as Customer 1 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + + <!--Create an order at Storefront as Customer 1 --> + <actionGroup ref="CreateOrderToPrintPageActionGroup" stepKey="createOrderToPrint"> + <argument name="Category" value="$$createCategory$$"/> + </actionGroup> + + <!--Go to 'print order' page by grabbed order id--> + <grabFromCurrentUrl regex="~/order_id/(\d+)/~" stepKey="grabOrderIdFromURL"/> + <switchToNextTab stepKey="switchToPrintPage"/> + <waitForElement selector="{{SalesOrderPrintSection.isOrderPrintPage}}" stepKey="checkPrintPage"/> + <openNewTab stepKey="openNewTab"/> + <switchToNextTab stepKey="switchForward"/> + <amOnPage url="{{StorefrontSalesOrderPrintPage.url({$grabOrderIdFromURL})}}" stepKey="duplicatePrintPage"/> + + <!--Log out as customer 1--> + <switchToNextTab stepKey="switchForward2"/> + <openNewTab stepKey="openNewTab2"/> + <amOnPage url="{{StorefrontCustomerSignOutPage.url}}" stepKey="signOut"/> + <waitForLoadingMaskToDisappear stepKey="waitSignOutPage"/> + + <!--Log in to Storefront as Customer 2 --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signUp2"> + <argument name="Customer" value="$$createCustomer2$$"/> + </actionGroup> + + <!--Create an order at Storefront as Customer 2 --> + <actionGroup ref="CreateOrderToPrintPageActionGroup" stepKey="createOrderToPrint2"> + <argument name="Category" value="$$createCategory$$"/> + </actionGroup> + + <!--Try to load 'print order' page with not relevant order id to be redirected to 'order history' page--> + <switchToNextTab stepKey="switchToPrintPage2"/> + <waitForElement selector="{{SalesOrderPrintSection.isOrderPrintPage}}" stepKey="checkPrintPage2"/> + <openNewTab stepKey="openNewTab3"/> + <switchToNextTab stepKey="switchForward4"/> + <amOnPage url="{{StorefrontSalesOrderPrintPage.url({$grabOrderIdFromURL})}}" stepKey="duplicatePrintPage2"/> + <seeElement selector="{{StorefrontCustomerOrderSection.isMyOrdersSection}}" stepKey="waitOrderHistoryPage"/> + </test> +</tests> diff --git a/app/code/Magento/Sales/view/frontend/layout/sales_guest_print.xml b/app/code/Magento/Sales/view/frontend/layout/sales_guest_print.xml index 0e2689e3e2191..99af16ce629d0 100644 --- a/app/code/Magento/Sales/view/frontend/layout/sales_guest_print.xml +++ b/app/code/Magento/Sales/view/frontend/layout/sales_guest_print.xml @@ -12,11 +12,20 @@ <body> <attribute name="class" value="sales-guest-view"/> <referenceContainer name="page.main.title"> - <block class="Magento\Sales\Block\Order\PrintShipment" name="order.status" template="Magento_Sales::order/order_status.phtml" /> - <block class="Magento\Sales\Block\Order\PrintShipment" name="order.date" template="Magento_Sales::order/order_date.phtml" /> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="order.status" + template="Magento_Sales::order/order_status.phtml" + cacheable="false" /> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="order.date" + template="Magento_Sales::order/order_date.phtml" + cacheable="false" /> </referenceContainer> <referenceContainer name="content"> - <block class="Magento\Sales\Block\Order\PrintShipment" name="sales.order.print" template="Magento_Sales::order/view.phtml"> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="sales.order.print" + template="Magento_Sales::order/view.phtml" + cacheable="false"> <block class="Magento\Sales\Block\Order\PrintShipment" name="order_items" template="Magento_Sales::order/items.phtml"> <block class="Magento\Framework\View\Element\RendererList" name="sales.order.print.renderers" as="renderer.list" /> <block class="Magento\Sales\Block\Order\Totals" name="order_totals" template="Magento_Sales::order/totals.phtml"> diff --git a/app/code/Magento/Sales/view/frontend/layout/sales_order_print.xml b/app/code/Magento/Sales/view/frontend/layout/sales_order_print.xml index f8d2e373e8af8..4410a6fc4a9a2 100644 --- a/app/code/Magento/Sales/view/frontend/layout/sales_order_print.xml +++ b/app/code/Magento/Sales/view/frontend/layout/sales_order_print.xml @@ -12,11 +12,20 @@ <body> <attribute name="class" value="account"/> <referenceContainer name="page.main.title"> - <block class="Magento\Sales\Block\Order\PrintShipment" cacheable="false" name="order.status" template="Magento_Sales::order/order_status.phtml" /> - <block class="Magento\Sales\Block\Order\PrintShipment" name="order.date" template="Magento_Sales::order/order_date.phtml" /> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="order.status" + template="Magento_Sales::order/order_status.phtml" + cacheable="false" /> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="order.date" + template="Magento_Sales::order/order_date.phtml" + cacheable="false" /> </referenceContainer> <referenceContainer name="content"> - <block class="Magento\Sales\Block\Order\PrintShipment" name="sales.order.print" template="Magento_Sales::order/view.phtml"> + <block class="Magento\Sales\Block\Order\PrintShipment" + name="sales.order.print" + template="Magento_Sales::order/view.phtml" + cacheable="false"> <block class="Magento\Sales\Block\Order\Items" name="order_items" template="Magento_Sales::order/items.phtml"> <block class="Magento\Framework\View\Element\RendererList" name="sales.order.print.renderers" as="renderer.list" /> <block class="Magento\Sales\Block\Order\Totals" name="order_totals" template="Magento_Sales::order/totals.phtml"> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml index a0eac56e4db09..06f6abb2973e7 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml @@ -18,9 +18,6 @@ <testCaseId value="MAGETWO-41931"/> <group value="checkout"/> <group value="tax"/> - <skip> - <issueId value="MAGETWO-90966"/> - </skip> </annotations> <before> <!-- Preconditions --> diff --git a/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php b/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php index c0e78fae2c2c6..565b312325ddc 100644 --- a/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php +++ b/app/code/Magento/User/Controller/Adminhtml/Auth/Forgotpassword.php @@ -6,8 +6,10 @@ */ namespace Magento\User\Controller\Adminhtml\Auth; -use Magento\Security\Model\SecurityManager; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\ObjectManager; +use Magento\Security\Model\SecurityManager; use Magento\Backend\App\Action\Context; use Magento\User\Model\UserFactory; use Magento\User\Model\ResourceModel\User\CollectionFactory; @@ -16,14 +18,25 @@ use Magento\Framework\Exception\SecurityViolationException; use Magento\User\Controller\Adminhtml\Auth; use Magento\Backend\Helper\Data; +use Magento\User\Model\Spi\NotificatorInterface; -class Forgotpassword extends Auth +/** + * Initiate forgot-password process. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Forgotpassword extends Auth implements HttpGetActionInterface, HttpPostActionInterface { /** * @var SecurityManager */ protected $securityManager; + /** + * @var NotificatorInterface + */ + private $notificator; + /** * User model factory * @@ -41,13 +54,16 @@ class Forgotpassword extends Auth * @param UserFactory $userFactory * @param SecurityManager $securityManager * @param CollectionFactory $userCollectionFactory + * @param Data $backendDataHelper + * @param NotificatorInterface|null $notificator */ public function __construct( Context $context, UserFactory $userFactory, SecurityManager $securityManager, CollectionFactory $userCollectionFactory = null, - Data $backendDataHelper = null + Data $backendDataHelper = null, + ?NotificatorInterface $notificator = null ) { parent::__construct($context, $userFactory); $this->securityManager = $securityManager; @@ -55,6 +71,8 @@ public function __construct( ObjectManager::getInstance()->get(CollectionFactory::class); $this->backendDataHelper = $backendDataHelper ?: ObjectManager::getInstance()->get(Data::class); + $this->notificator = $notificator + ?? ObjectManager::getInstance()->get(NotificatorInterface::class); } /** @@ -96,7 +114,7 @@ public function execute() $newPassResetToken = $this->backendDataHelper->generateResetPasswordLinkToken(); $user->changeResetPasswordLinkToken($newPassResetToken); $user->save(); - $user->sendPasswordResetConfirmationEmail(); + $this->notificator->sendForgotPassword($user); } break; } diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Save.php b/app/code/Magento/User/Controller/Adminhtml/User/Save.php index 09061384a8e61..521c09f7b7707 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Save.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Save.php @@ -10,8 +10,11 @@ use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Exception\State\UserLockedException; use Magento\Security\Model\SecurityCookie; +use Magento\User\Model\Spi\NotificationExceptionInterface; /** + * Save admin user. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Save extends \Magento\User\Controller\Adminhtml\User implements HttpPostActionInterface @@ -37,7 +40,7 @@ private function getSecurityCookie() } /** - * @return void + * @inheritDoc * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -92,17 +95,19 @@ public function execute() $currentUser->performIdentityCheck($data[$currentUserPasswordField]); $model->save(); - $model->sendNotificationEmailsIfRequired(); - $this->messageManager->addSuccess(__('You saved the user.')); $this->_getSession()->setUserData(false); $this->_redirect('adminhtml/*/'); + + $model->sendNotificationEmailsIfRequired(); } catch (UserLockedException $e) { $this->_auth->logout(); $this->getSecurityCookie()->setLogoutReasonCookie( \Magento\Security\Model\AdminSessionsManager::LOGOUT_REASON_USER_LOCKED ); $this->_redirect('adminhtml/*/'); + } catch (NotificationExceptionInterface $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); } catch (\Magento\Framework\Exception\AuthenticationException $e) { $this->messageManager->addError( __('The password entered for the current user is invalid. Verify the password and try again.') @@ -121,6 +126,8 @@ public function execute() } /** + * Redirect to Edit form. + * * @param \Magento\User\Model\User $model * @param array $data * @return void diff --git a/app/code/Magento/User/Model/Notificator.php b/app/code/Magento/User/Model/Notificator.php new file mode 100644 index 0000000000000..3a5522db4c533 --- /dev/null +++ b/app/code/Magento/User/Model/Notificator.php @@ -0,0 +1,194 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\User\Model; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\MailException; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\User\Api\Data\UserInterface; +use Magento\User\Model\Spi\NotificatorInterface; +use Magento\Backend\App\ConfigInterface; +use Magento\Framework\Mail\Template\TransportBuilder; +use Magento\Framework\App\DeploymentConfig; +use Magento\Backend\App\Area\FrontNameResolver; +use Magento\Email\Model\BackendTemplate; + +/** + * @inheritDoc + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Notificator implements NotificatorInterface +{ + /** + * @var TransportBuilder + */ + private $transportBuilder; + + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var DeploymentConfig + */ + private $deployConfig; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param TransportBuilder $transportBuilder + * @param ConfigInterface $config + * @param DeploymentConfig $deployConfig + * @param StoreManagerInterface $storeManager + */ + public function __construct( + TransportBuilder $transportBuilder, + ConfigInterface $config, + DeploymentConfig $deployConfig, + StoreManagerInterface $storeManager + ) { + $this->transportBuilder = $transportBuilder; + $this->config = $config; + $this->deployConfig = $deployConfig; + $this->storeManager = $storeManager; + } + + /** + * Send a notification. + * + * @param string $templateConfigId + * @param array $templateVars + * @param string $toEmail + * @param string $toName + * @throws MailException + * + * @return void + */ + private function sendNotification( + string $templateConfigId, + array $templateVars, + string $toEmail, + string $toName + ): void { + $transport = $this->transportBuilder + ->setTemplateIdentifier($this->config->getValue($templateConfigId)) + ->setTemplateModel(BackendTemplate::class) + ->setTemplateOptions([ + 'area' => FrontNameResolver::AREA_CODE, + 'store' => Store::DEFAULT_STORE_ID + ]) + ->setTemplateVars($templateVars) + ->setFrom( + $this->config->getValue('admin/emails/forgot_email_identity') + ) + ->addTo($toEmail, $toName) + ->getTransport(); + $transport->sendMessage(); + } + + /** + * @inheritDoc + */ + public function sendForgotPassword(UserInterface $user): void + { + try { + $this->sendNotification( + 'admin/emails/forgot_email_template', + [ + 'user' => $user, + 'store' => $this->storeManager->getStore( + Store::DEFAULT_STORE_ID + ) + ], + $user->getEmail(), + $user->getFirstName().' '.$user->getLastName() + ); + } catch (LocalizedException $exception) { + throw new NotificatorException( + __($exception->getMessage()), + $exception + ); + } + } + + /** + * @inheritDoc + */ + public function sendCreated(UserInterface $user): void + { + $toEmails = []; + $generalEmail = $this->config->getValue( + 'trans_email/ident_general/email' + ); + if ($generalEmail) { + $toEmails[] = $generalEmail; + } + if ($adminEmail = $this->deployConfig->get('user_admin_email')) { + $toEmails[] = $adminEmail; + } + + try { + foreach ($toEmails as $toEmail) { + $this->sendNotification( + 'admin/emails/new_user_notification_template', + [ + 'user' => $user, + 'store' => $this->storeManager->getStore( + Store::DEFAULT_STORE_ID + ) + ], + $toEmail, + __('Administrator')->getText() + ); + } + } catch (LocalizedException $exception) { + throw new NotificatorException( + __($exception->getMessage()), + $exception + ); + } + } + + /** + * @inheritDoc + */ + public function sendUpdated(UserInterface $user, array $changed): void + { + $email = $user->getEmail(); + if ($user instanceof User) { + $email = $user->getOrigData('email'); + } + + try { + $this->sendNotification( + 'admin/emails/user_notification_template', + [ + 'user' => $user, + 'store' => $this->storeManager->getStore( + Store::DEFAULT_STORE_ID + ), + 'changes' => implode(', ', $changed) + ], + $email, + $user->getFirstName().' '.$user->getLastName() + ); + } catch (LocalizedException $exception) { + throw new NotificatorException( + __($exception->getMessage()), + $exception + ); + } + } +} diff --git a/app/code/Magento/User/Model/NotificatorException.php b/app/code/Magento/User/Model/NotificatorException.php new file mode 100644 index 0000000000000..5b581879814ab --- /dev/null +++ b/app/code/Magento/User/Model/NotificatorException.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\User\Model; + +use Magento\Framework\Exception\MailException; +use Magento\User\Model\Spi\NotificationExceptionInterface; + +/** + * When notificator cannot send an email. + */ +class NotificatorException extends MailException implements NotificationExceptionInterface +{ + +} diff --git a/app/code/Magento/User/Model/Spi/NotificationExceptionInterface.php b/app/code/Magento/User/Model/Spi/NotificationExceptionInterface.php new file mode 100644 index 0000000000000..47af7cbe6dd50 --- /dev/null +++ b/app/code/Magento/User/Model/Spi/NotificationExceptionInterface.php @@ -0,0 +1,17 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\User\Model\Spi; + +/** + * When a notification cannot be sent. + */ +interface NotificationExceptionInterface extends \Throwable +{ + +} diff --git a/app/code/Magento/User/Model/Spi/NotificatorInterface.php b/app/code/Magento/User/Model/Spi/NotificatorInterface.php new file mode 100644 index 0000000000000..f5e8ee68e7eed --- /dev/null +++ b/app/code/Magento/User/Model/Spi/NotificatorInterface.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\User\Model\Spi; + +use Magento\User\Api\Data\UserInterface; + +/** + * Use to send out notifications about user related events. + */ +interface NotificatorInterface +{ + /** + * Send notification when a user requests password reset. + * + * @param UserInterface $user User that requested password reset. + * @throws NotificationExceptionInterface + * + * @return void + */ + public function sendForgotPassword(UserInterface $user): void; + + /** + * Send a notification when a new user is created. + * + * @param UserInterface $user The new user. + * @throws NotificationExceptionInterface + * + * @return void + */ + public function sendCreated(UserInterface $user): void; + + /** + * Send a notification when a user is updated. + * + * @param UserInterface $user The user updated. + * @param string[] $changed List of changed properties. + * @throws NotificationExceptionInterface + * + * @return void + */ + public function sendUpdated(UserInterface $user, array $changed): void; +} diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index 29618c981a2b9..4050d79e593ee 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -6,14 +6,15 @@ namespace Magento\User\Model; -use Magento\Backend\App\Area\FrontNameResolver; use Magento\Backend\Model\Auth\Credential\StorageInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Store\Model\Store; use Magento\User\Api\Data\UserInterface; +use Magento\User\Model\Spi\NotificationExceptionInterface; +use Magento\User\Model\Spi\NotificatorInterface; +use Magento\Framework\App\DeploymentConfig; /** * Admin user model @@ -37,12 +38,21 @@ class User extends AbstractModel implements StorageInterface, UserInterface { /** - * Configuration paths for email templates and identities + * @deprecated + * @see \Magento\User\Model\Spi\NotificatorInterface */ const XML_PATH_FORGOT_EMAIL_TEMPLATE = 'admin/emails/forgot_email_template'; + /** + * @deprecated + * @see \Magento\User\Model\Spi\NotificatorInterface + */ const XML_PATH_FORGOT_EMAIL_IDENTITY = 'admin/emails/forgot_email_identity'; + /** + * @deprecated + * @see \Magento\User\Model\Spi\NotificatorInterface + */ const XML_PATH_USER_NOTIFICATION_TEMPLATE = 'admin/emails/user_notification_template'; /** @deprecated */ @@ -105,12 +115,12 @@ class User extends AbstractModel implements StorageInterface, UserInterface protected $_encryptor; /** - * @var \Magento\Framework\Mail\Template\TransportBuilder + * @deprecated */ protected $_transportBuilder; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @deprecated */ protected $_storeManager; @@ -124,6 +134,16 @@ class User extends AbstractModel implements StorageInterface, UserInterface */ private $serializer; + /** + * @var NotificatorInterface + */ + private $notificator; + + /** + * @deprecated + */ + private $deploymentConfig; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -139,6 +159,8 @@ class User extends AbstractModel implements StorageInterface, UserInterface * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data * @param Json $serializer + * @param DeploymentConfig|null $deploymentConfig + * @param NotificatorInterface|null $notificator * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -155,7 +177,9 @@ public function __construct( \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, array $data = [], - Json $serializer = null + Json $serializer = null, + DeploymentConfig $deploymentConfig = null, + ?NotificatorInterface $notificator = null ) { $this->_encryptor = $encryptor; parent::__construct($context, $registry, $resource, $resourceCollection, $data); @@ -166,7 +190,12 @@ public function __construct( $this->_transportBuilder = $transportBuilder; $this->_storeManager = $storeManager; $this->validationRules = $validationRules; - $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + $this->serializer = $serializer + ?: ObjectManager::getInstance()->get(Json::class); + $this->deploymentConfig = $deploymentConfig + ?: ObjectManager::getInstance()->get(DeploymentConfig::class); + $this->notificator = $notificator + ?: ObjectManager::getInstance()->get(NotificatorInterface::class); } /** @@ -180,6 +209,8 @@ protected function _construct() } /** + * Removing dependencies and leaving only entity's properties. + * * @return string[] */ public function __sleep() @@ -196,12 +227,18 @@ public function __sleep() '_encryptor', '_transportBuilder', '_storeManager', - '_validatorBeforeSave' + '_validatorBeforeSave', + 'validationRules', + 'serializer', + 'deploymentConfig', + 'notificator' ] ); } /** + * Restoring required objects after serialization. + * * @return void */ public function __wakeup() @@ -218,6 +255,9 @@ public function __wakeup() $this->_encryptor = $objectManager->get(\Magento\Framework\Encryption\EncryptorInterface::class); $this->_transportBuilder = $objectManager->get(\Magento\Framework\Mail\Template\TransportBuilder::class); $this->_storeManager = $objectManager->get(\Magento\Store\Model\StoreManagerInterface::class); + $this->validationRules = $objectManager->get(UserValidationRules::class); + $this->deploymentConfig = $objectManager->get(DeploymentConfig::class); + $this->notificator = $objectManager->get(NotificatorInterface::class); } /** @@ -388,7 +428,7 @@ public function deleteFromRole() } /** - * Check if such combination role/user exists + * Check if such combination role/user exists. * * @return bool */ @@ -399,28 +439,24 @@ public function roleUserExists() } /** - * Send email with reset password confirmation link + * Send email with reset password confirmation link. + * + * @deprecated + * @see NotificatorInterface::sendForgotPassword() * * @return $this */ public function sendPasswordResetConfirmationEmail() { - $templateId = $this->_config->getValue(self::XML_PATH_FORGOT_EMAIL_TEMPLATE); - $transport = $this->_transportBuilder->setTemplateIdentifier($templateId) - ->setTemplateModel(\Magento\Email\Model\BackendTemplate::class) - ->setTemplateOptions(['area' => FrontNameResolver::AREA_CODE, 'store' => Store::DEFAULT_STORE_ID]) - ->setTemplateVars(['user' => $this, 'store' => $this->_storeManager->getStore(Store::DEFAULT_STORE_ID)]) - ->setFrom($this->_config->getValue(self::XML_PATH_FORGOT_EMAIL_IDENTITY)) - ->addTo($this->getEmail(), $this->getName()) - ->getTransport(); + $this->notificator->sendForgotPassword($this); - $transport->sendMessage(); return $this; } /** * Send email to when password is resetting * + * @throws NotificationExceptionInterface * @return $this * @deprecated 100.1.0 */ @@ -431,20 +467,20 @@ public function sendPasswordResetNotificationEmail() } /** - * Check changes and send notification emails + * Check changes and send notification emails. * + * @throws NotificationExceptionInterface * @return $this * @since 100.1.0 */ public function sendNotificationEmailsIfRequired() { - $changes = $this->createChangesDescriptionString(); - - if ($changes) { - if ($this->getEmail() != $this->getOrigData('email') && $this->getOrigData('email')) { - $this->sendUserNotificationEmail($changes, $this->getOrigData('email')); - } - $this->sendUserNotificationEmail($changes); + if ($this->isObjectNew()) { + //Notification about a new user. + $this->notificator->sendCreated($this); + } elseif ($changes = $this->createChangesDescriptionString()) { + //User changed. + $this->notificator->sendUpdated($this, explode(', ', $changes)); } return $this; @@ -478,35 +514,21 @@ protected function createChangesDescriptionString() } /** - * Send user notification email + * Send user notification email. * * @param string $changes * @param string $email + * @throws NotificationExceptionInterface * @return $this * @since 100.1.0 + * @deprecated + * @see NotificatorInterface::sendUpdated() + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ protected function sendUserNotificationEmail($changes, $email = null) { - if ($email === null) { - $email = $this->getEmail(); - } + $this->notificator->sendUpdated($this, explode(', ', $changes)); - $transport = $this->_transportBuilder - ->setTemplateIdentifier($this->_config->getValue(self::XML_PATH_USER_NOTIFICATION_TEMPLATE)) - ->setTemplateModel(\Magento\Email\Model\BackendTemplate::class) - ->setTemplateOptions(['area' => FrontNameResolver::AREA_CODE, 'store' => Store::DEFAULT_STORE_ID]) - ->setTemplateVars( - [ - 'user' => $this, - 'store' => $this->_storeManager->getStore(Store::DEFAULT_STORE_ID), - 'changes' => $changes - ] - ) - ->setFrom($this->_config->getValue(self::XML_PATH_FORGOT_EMAIL_IDENTITY)) - ->addTo($email, $this->getName()) - ->getTransport(); - - $transport->sendMessage(); return $this; } @@ -738,7 +760,7 @@ public function setHasAvailableResources($hasResources) } /** - * {@inheritdoc} + * @inheritDoc */ public function getFirstName() { @@ -746,7 +768,7 @@ public function getFirstName() } /** - * {@inheritdoc} + * @inheritDoc */ public function setFirstName($firstName) { @@ -754,7 +776,7 @@ public function setFirstName($firstName) } /** - * {@inheritdoc} + * @inheritDoc */ public function getLastName() { @@ -762,7 +784,7 @@ public function getLastName() } /** - * {@inheritdoc} + * @inheritDoc */ public function setLastName($lastName) { @@ -770,7 +792,7 @@ public function setLastName($lastName) } /** - * {@inheritdoc} + * @inheritDoc */ public function getEmail() { @@ -778,7 +800,7 @@ public function getEmail() } /** - * {@inheritdoc} + * @inheritDoc */ public function setEmail($email) { @@ -786,7 +808,7 @@ public function setEmail($email) } /** - * {@inheritdoc} + * @inheritDoc */ public function getUserName() { @@ -794,7 +816,7 @@ public function getUserName() } /** - * {@inheritdoc} + * @inheritDoc */ public function setUserName($userName) { @@ -802,7 +824,7 @@ public function setUserName($userName) } /** - * {@inheritdoc} + * @inheritDoc */ public function getPassword() { @@ -810,7 +832,7 @@ public function getPassword() } /** - * {@inheritdoc} + * @inheritDoc */ public function setPassword($password) { @@ -818,7 +840,7 @@ public function setPassword($password) } /** - * {@inheritdoc} + * @inheritDoc */ public function getCreated() { @@ -826,7 +848,7 @@ public function getCreated() } /** - * {@inheritdoc} + * @inheritDoc */ public function setCreated($created) { @@ -834,7 +856,7 @@ public function setCreated($created) } /** - * {@inheritdoc} + * @inheritDoc */ public function getModified() { @@ -842,7 +864,7 @@ public function getModified() } /** - * {@inheritdoc} + * @inheritDoc */ public function setModified($modified) { @@ -850,7 +872,7 @@ public function setModified($modified) } /** - * {@inheritdoc} + * @inheritDoc */ public function getIsActive() { @@ -858,7 +880,7 @@ public function getIsActive() } /** - * {@inheritdoc} + * @inheritDoc */ public function setIsActive($isActive) { @@ -866,7 +888,7 @@ public function setIsActive($isActive) } /** - * {@inheritdoc} + * @inheritDoc */ public function getInterfaceLocale() { @@ -874,7 +896,7 @@ public function getInterfaceLocale() } /** - * {@inheritdoc} + * @inheritDoc */ public function setInterfaceLocale($interfaceLocale) { diff --git a/app/code/Magento/User/Test/Unit/Model/UserTest.php b/app/code/Magento/User/Test/Unit/Model/UserTest.php index 4bc5db138c7ba..670316c2500fc 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserTest.php @@ -6,7 +6,9 @@ namespace Magento\User\Test\Unit\Model; -use Magento\Framework\Serialize\Serializer\Json; +use Magento\User\Helper\Data as UserHelper; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\User\Model\User; /** * Test class for \Magento\User\Model\User testing @@ -16,55 +18,11 @@ */ class UserTest extends \PHPUnit\Framework\TestCase { - /** @var \Magento\User\Model\User */ - protected $model; + /** @var User */ + private $model; - /** @var \Magento\User\Helper\Data|\PHPUnit_Framework_MockObject_MockObject */ - protected $userDataMock; - - /** @var \Magento\Framework\Mail\Template\TransportBuilder|\PHPUnit_Framework_MockObject_MockObject */ - protected $transportBuilderMock; - - /** @var \Magento\Framework\Model\Context|\PHPUnit_Framework_MockObject_MockObject */ - protected $contextMock; - - /** @var \Magento\User\Model\ResourceModel\User|\PHPUnit_Framework_MockObject_MockObject */ - protected $resourceMock; - - /** @var \Magento\Framework\Data\Collection\AbstractDb|\PHPUnit_Framework_MockObject_MockObject */ - protected $collectionMock; - - /** @var \Magento\Framework\Mail\TransportInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $transportMock; - - /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeManagerMock; - - /** @var \Magento\Store\Model\Store|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeMock; - - /** @var \Magento\Backend\App\ConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $configMock; - - /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $encryptorMock; - - /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $eventManagerMock; - - /** @var \Magento\Framework\Validator\DataObjectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $validatorObjectFactoryMock; - - /** @var \Magento\User\Model\UserValidationRules|\PHPUnit_Framework_MockObject_MockObject */ - protected $validationRulesMock; - - /** @var \Magento\Authorization\Model\RoleFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $roleFactoryMock; - - /** - * @var Json|\PHPUnit_Framework_MockObject_MockObject - */ - private $serializer; + /** @var UserHelper|\PHPUnit_Framework_MockObject_MockObject */ + private $userDataMock; /** * Set required values @@ -72,308 +30,20 @@ class UserTest extends \PHPUnit\Framework\TestCase */ protected function setUp() { - $this->userDataMock = $this->getMockBuilder(\Magento\User\Helper\Data::class) + $this->userDataMock = $this->getMockBuilder(UserHelper::class) ->disableOriginalConstructor() ->setMethods([]) ->getMock(); - $this->contextMock = $this->getMockBuilder(\Magento\Framework\Model\Context::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->resourceMock = $this->getMockBuilder(\Magento\User\Model\ResourceModel\User::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->collectionMock = $this->getMockBuilder(\Magento\Framework\Data\Collection\AbstractDb::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMockForAbstractClass(); - $coreRegistry = $this->getMockBuilder(\Magento\Framework\Registry::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) - ->disableOriginalConstructor() - ->setMethods(['dispatch']) - ->getMockForAbstractClass(); - $this->validatorObjectFactoryMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObjectFactory::class) - ->disableOriginalConstructor()->setMethods(['create']) - ->getMock(); - $this->roleFactoryMock = $this->getMockBuilder(\Magento\Authorization\Model\RoleFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->transportMock = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->transportBuilderMock = $this->getMockBuilder(\Magento\Framework\Mail\Template\TransportBuilder::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->configMock = $this->getMockBuilder(\Magento\Backend\App\ConfigInterface::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->validationRulesMock = $this->getMockBuilder(\Magento\User\Model\UserValidationRules::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - - $this->encryptorMock = $this->getMockBuilder(\Magento\Framework\Encryption\EncryptorInterface::class) - ->setMethods(['validateHash']) - ->getMockForAbstractClass(); - - $this->serializer = $this->createPartialMock(Json::class, ['serialize', 'unserialize']); - $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $objectManagerHelper = new ObjectManager($this); $this->model = $objectManagerHelper->getObject( - \Magento\User\Model\User::class, + User::class, [ - 'eventManager' => $this->eventManagerMock, 'userData' => $this->userDataMock, - 'registry' => $coreRegistry, - 'resource' => $this->resourceMock, - 'resourceCollection' => $this->collectionMock, - 'validatorObjectFactory' => $this->validatorObjectFactoryMock, - 'roleFactory' => $this->roleFactoryMock, - 'transportBuilder' => $this->transportBuilderMock, - 'storeManager' => $this->storeManagerMock, - 'validationRules' => $this->validationRulesMock, - 'config' => $this->configMock, - 'encryptor' => $this->encryptorMock, - 'serializer' => $this->serializer ] ); } - /** - * @return void - */ - public function testSendNotificationEmailsIfRequired() - { - $storeId = 0; - $email = 'test1@example.com'; - $origEmail = 'test2@example.com'; - - $password = '1234567'; - $origPassword = '123456789'; - - $username = 'admin1'; - $origUsername = 'admin2'; - - $firstName = 'Foo'; - $lastName = 'Bar'; - - $changes = __('email') . ', ' . __('password') . ', ' . __('username'); - - $this->model->setEmail($email); - $this->model->setOrigData('email', $origEmail); - - $this->model->setPassword($password); - $this->model->setOrigData('password', $origPassword); - - $this->model->setUserName($username); - $this->model->setOrigData('username', $origUsername); - - $this->model->setFirstName($firstName); - $this->model->setLastName($lastName); - - $this->configMock->expects($this->exactly(4)) - ->method('getValue') - ->withConsecutive( - [\Magento\User\Model\User::XML_PATH_USER_NOTIFICATION_TEMPLATE], - [\Magento\User\Model\User::XML_PATH_FORGOT_EMAIL_IDENTITY], - [\Magento\User\Model\User::XML_PATH_USER_NOTIFICATION_TEMPLATE], - [\Magento\User\Model\User::XML_PATH_FORGOT_EMAIL_IDENTITY] - )->willReturnOnConsecutiveCalls( - 'templateId', - 'sender', - 'templateId', - 'sender' - ); - - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setTemplateModel') - ->with($this->equalTo(\Magento\Email\Model\BackendTemplate::class)) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setTemplateOptions') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setTemplateVars') - ->with(['user' => $this->model, 'store' => $this->storeMock, 'changes' => $changes]) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('addTo') - ->withConsecutive( - $this->equalTo($email), - $this->equalTo($firstName . ' ' . $lastName), - $this->equalTo($origEmail), - $this->equalTo($firstName . ' ' . $lastName) - ) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setFrom') - ->with('sender') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('setTemplateIdentifier') - ->with('templateId') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->exactly(2)) - ->method('getTransport') - ->willReturn($this->transportMock); - $this->transportMock->expects($this->exactly(2))->method('sendMessage'); - - $this->storeManagerMock->expects($this->exactly(2)) - ->method('getStore') - ->with($storeId) - ->willReturn($this->storeMock); - - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->sendNotificationEmailsIfRequired()); - } - - /** - * @return void - */ - public function testSendPasswordResetConfirmationEmail() - { - $storeId = 0; - $email = 'test@example.com'; - $firstName = 'Foo'; - $lastName = 'Bar'; - - $this->model->setEmail($email); - $this->model->setFirstName($firstName); - $this->model->setLastName($lastName); - - $this->configMock->expects($this->at(0)) - ->method('getValue') - ->with(\Magento\User\Model\User::XML_PATH_FORGOT_EMAIL_TEMPLATE) - ->willReturn('templateId'); - $this->configMock->expects($this->at(1)) - ->method('getValue') - ->with(\Magento\User\Model\User::XML_PATH_FORGOT_EMAIL_IDENTITY) - ->willReturn('sender'); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateModel') - ->with($this->equalTo(\Magento\Email\Model\BackendTemplate::class)) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateOptions') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateVars') - ->with(['user' => $this->model, 'store' => $this->storeMock]) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('addTo') - ->with($this->equalTo($email), $this->equalTo($firstName . ' ' . $lastName)) - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setFrom') - ->with('sender') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('setTemplateIdentifier') - ->with('templateId') - ->willReturnSelf(); - $this->transportBuilderMock->expects($this->once()) - ->method('getTransport') - ->willReturn($this->transportMock); - $this->transportMock->expects($this->once())->method('sendMessage'); - - $this->storeManagerMock->expects($this->once()) - ->method('getStore') - ->with($storeId) - ->willReturn($this->storeMock); - - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->sendPasswordResetConfirmationEmail()); - } - - /** - * @return void - */ - public function testVerifyIdentity() - { - $password = 'password'; - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn(true); - $this->model->setIsActive(true); - $this->resourceMock->expects($this->once())->method('hasAssigned2Role')->willReturn(true); - $this->assertTrue( - $this->model->verifyIdentity($password), - 'Identity verification failed while should have passed.' - ); - } - - /** - * @return void - */ - public function testVerifyIdentityFailure() - { - $password = 'password'; - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn(false); - $this->assertFalse( - $this->model->verifyIdentity($password), - 'Identity verification passed while should have failed.' - ); - } - - /** - * @return void - */ - public function testVerifyIdentityInactiveRecord() - { - $password = 'password'; - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn(true); - $this->model->setIsActive(false); - $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); - $this->expectExceptionMessage('The account sign-in was incorrect or your account is disabled temporarily. ' - . 'Please wait and try again later.'); - $this->model->verifyIdentity($password); - } - - /** - * @return void - */ - public function testVerifyIdentityNoAssignedRoles() - { - $password = 'password'; - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn(true); - $this->model->setIsActive(true); - $this->resourceMock->expects($this->once())->method('hasAssigned2Role')->willReturn(false); - $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); - $this->expectExceptionMessage('More permissions are needed to access this.'); - $this->model->verifyIdentity($password); - } - /** * @return void */ @@ -399,225 +69,21 @@ public function testSleep() $this->assertEmpty($expectedResult); } - /** - * @return void - */ - public function testBeforeSave() - { - $this->eventManagerMock->expects($this->any())->method('dispatch'); - $this->model->setIsActive(1); - $actualData = $this->model->beforeSave()->getData(); - $this->assertArrayHasKey('extra', $actualData); - $this->assertArrayHasKey('password', $actualData); - $this->assertArrayHasKey('is_active', $actualData); - } - - /** - * @return void - */ - public function testValidateOk() - { - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(true); - $this->assertTrue($this->model->validate()); - } - - /** - * @return void - */ - public function testValidateInvalid() - { - $messages = ['Invalid username']; - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(false); - $validatorMock->expects($this->once())->method('getMessages')->willReturn($messages); - $this->assertEquals($messages, $this->model->validate()); - } - - /** - * @return void - */ - public function testSaveExtra() - { - $data = [1, 2, 3]; - $this->resourceMock->expects($this->once()) - ->method('saveExtra') - ->with($this->model, json_encode($data)); - - $this->serializer->expects($this->once()) - ->method('serialize') - ->with($data) - ->will($this->returnValue(json_encode($data))); - - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->saveExtra($data)); - } - - /** - * @return void - */ - public function testGetRoles() - { - $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn([]); - $this->assertInternalType('array', $this->model->getRoles()); - } - - /** - * @return void - */ - public function testGetRole() - { - $roles = ['role']; - $roleMock = $this->getMockBuilder(\Magento\Authorization\Model\Role::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->roleFactoryMock->expects($this->once())->method('create')->willReturn($roleMock); - $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn($roles); - $roleMock->expects($this->once())->method('load')->with($roles[0]); - $this->assertInstanceOf(\Magento\Authorization\Model\Role::class, $this->model->getRole()); - } - - /** - * @return void - */ - public function testDeleteFromRole() - { - $this->resourceMock->expects($this->once())->method('deleteFromRole')->with($this->model); - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->deleteFromRole()); - } - - /** - * @return void - */ - public function testRoleUserExistsTrue() - { - $result = ['role']; - $this->resourceMock->expects($this->once())->method('roleUserExists')->with($this->model)->willReturn($result); - $this->assertTrue($this->model->roleUserExists()); - } - - /** - * @return void - */ - public function testRoleUserExistsFalse() - { - $result = []; - $this->resourceMock->expects($this->once())->method('roleUserExists')->with($this->model)->willReturn($result); - $this->assertFalse($this->model->roleUserExists()); - } - - /** - * @return void - */ - public function testGetAclRole() - { - $roles = ['role']; - $result = 1; - $roleMock = $this->getMockBuilder(\Magento\Authorization\Model\Role::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->roleFactoryMock->expects($this->once())->method('create')->willReturn($roleMock); - $this->resourceMock->expects($this->once())->method('getRoles')->with($this->model)->willReturn($roles); - $roleMock->expects($this->once())->method('load')->with($roles[0]); - $roleMock->expects($this->once())->method('getId')->willReturn($result); - $this->assertEquals($result, $this->model->getAclRole()); - } - - /** - * @dataProvider authenticateDataProvider - * @param string $usernameIn - * @param string $usernameOut - * @param bool $expectedResult - * @return void - */ - public function testAuthenticate($usernameIn, $usernameOut, $expectedResult) - { - $password = 'password'; - $config = 'config'; - - $data = ['id' => 1, 'is_active' => 1, 'username' => $usernameOut]; - - $this->configMock->expects($this->once()) - ->method('isSetFlag') - ->with('admin/security/use_case_sensitive_login') - ->willReturn($config); - $this->eventManagerMock->expects($this->any())->method('dispatch'); - - $this->resourceMock->expects($this->any())->method('loadByUsername')->willReturn($data); - $this->model->setIdFieldName('id'); - - $this->encryptorMock->expects($this->any())->method('validateHash')->willReturn(true); - $this->resourceMock->expects($this->any())->method('hasAssigned2Role')->willReturn(true); - $this->assertEquals($expectedResult, $this->model->authenticate($usernameIn, $password)); - } - - /** - * @return array - */ - public function authenticateDataProvider() - { - return [ - 'success' => [ - 'usernameIn' => 'username', - 'usernameOut' => 'username', - 'expectedResult' => true - ], - 'failedUsername' => [ - 'usernameIn' => 'username1', - 'usernameOut' => 'username2', - 'expectedResult' => false - ] - ]; - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - * @return void - */ - public function testAuthenticateException() - { - $username = 'username'; - $password = 'password'; - $config = 'config'; - - $this->configMock->expects($this->once()) - ->method('isSetFlag') - ->with('admin/security/use_case_sensitive_login') - ->willReturn($config); - - $this->eventManagerMock->expects($this->any())->method('dispatch'); - $this->resourceMock->expects($this->once()) - ->method('loadByUsername') - ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__())); - $this->model->authenticate($username, $password); - } - /** * @return void */ public function testChangeResetPasswordLinkToken() { $token = '1'; - $this->assertInstanceOf(\Magento\User\Model\User::class, $this->model->changeResetPasswordLinkToken($token)); + $this->assertInstanceOf( + User::class, + $this->model->changeResetPasswordLinkToken($token) + ); $this->assertEquals($token, $this->model->getRpToken()); - $this->assertInternalType('string', $this->model->getRpTokenCreatedAt()); + $this->assertInternalType( + 'string', + $this->model->getRpTokenCreatedAt() + ); } /** @@ -640,168 +106,4 @@ public function testIsResetPasswordLinkTokenExpiredIsExpiredToken() $this->userDataMock->expects($this->once())->method('getResetPasswordLinkExpirationPeriod')->willReturn(0); $this->assertTrue($this->model->isResetPasswordLinkTokenExpired()); } - - /** - * @return void - */ - public function testIsResetPasswordLinkTokenExpiredIsNotExpiredToken() - { - $this->model->setRpToken('1'); - $this->model->setRpTokenCreatedAt( - (new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) - ); - $this->userDataMock->expects($this->once())->method('getResetPasswordLinkExpirationPeriod')->willReturn(1); - $this->assertFalse($this->model->isResetPasswordLinkTokenExpired()); - } - - public function testCheckPasswordChangeEqualToCurrent() - { - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(true); - - $newPassword = "NEWmYn3wpassw0rd"; - $oldPassword = "OLDmYn3wpassw0rd"; - $this->model->setPassword($newPassword) - ->setId(1) - ->setOrigData('password', $oldPassword); - $this->encryptorMock->expects($this->once()) - ->method('isValidHash') - ->with($newPassword, $oldPassword) - ->willReturn(true); - $result = $this->model->validate(); - $this->assertInternalType('array', $result); - $this->assertCount(1, $result); - $this->assertContains("Sorry, but this password has already been used.", (string)$result[0]); - } - - public function testCheckPasswordChangeEqualToPrevious() - { - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(true); - - $newPassword = "NEWmYn3wpassw0rd"; - $newPasswordHash = "new password hash"; - $oldPassword = "OLDmYn3wpassw0rd"; - $this->model->setPassword($newPassword) - ->setId(1) - ->setOrigData('password', $oldPassword); - $this->encryptorMock->expects($this->atLeastOnce()) - ->method('isValidHash') - ->will($this->onConsecutiveCalls(false, true)); - - $this->resourceMock->expects($this->once())->method('getOldPasswords')->willReturn(['hash1', $newPasswordHash]); - - $result = $this->model->validate(); - $this->assertInternalType('array', $result); - $this->assertCount(1, $result); - $this->assertContains("Sorry, but this password has already been used.", (string)$result[0]); - } - - public function testCheckPasswordChangeValid() - { - /** @var $validatorMock \Magento\Framework\Validator\DataObject|\PHPUnit_Framework_MockObject_MockObject */ - $validatorMock = $this->getMockBuilder(\Magento\Framework\Validator\DataObject::class) - ->disableOriginalConstructor() - ->setMethods([]) - ->getMock(); - $this->validatorObjectFactoryMock->expects($this->once())->method('create')->willReturn($validatorMock); - $this->validationRulesMock->expects($this->once()) - ->method('addUserInfoRules') - ->with($validatorMock); - $validatorMock->expects($this->once())->method('isValid')->willReturn(true); - - $newPassword = "NEWmYn3wpassw0rd"; - $oldPassword = "OLDmYn3wpassw0rd"; - $this->model->setPassword($newPassword) - ->setId(1) - ->setOrigData('password', $oldPassword); - $this->encryptorMock->expects($this->atLeastOnce()) - ->method('isValidHash') - ->will($this->onConsecutiveCalls(false, false, false)); - - $this->resourceMock->expects($this->once())->method('getOldPasswords')->willReturn(['hash1', 'hash2']); - - $result = $this->model->validate(); - $this->assertTrue($result); - } - - /** - * Test for performIdentityCheck method - * - * @param bool $verifyIdentityResult - * @param bool $lockExpires - * @dataProvider dataProviderPerformIdentityCheck - */ - public function testPerformIdentityCheck($verifyIdentityResult, $lockExpires) - { - $password = 'qwerty1'; - $userName = 'John Doe'; - - $this->encryptorMock - ->expects($this->once()) - ->method('validateHash') - ->with($password, $this->model->getPassword()) - ->willReturn($verifyIdentityResult); - $this->model->setIsActive(true); - $this->resourceMock->expects($this->any())->method('hasAssigned2Role')->willReturn(true); - - $this->model->setUserName($userName); - $this->model->setLockExpires($lockExpires); - - $this->eventManagerMock->expects($this->any()) - ->method('dispatch') - ->with( - 'admin_user_authenticate_after', - [ - 'username' => $userName, - 'password' => $password, - 'user' => $this->model, - 'result' => $verifyIdentityResult - ] - ) - ->willReturnSelf(); - - if ($lockExpires) { - $this->expectException(\Magento\Framework\Exception\State\UserLockedException::class); - $this->expectExceptionMessage((string)__('Your account is temporarily disabled. Please try again later.')); - } - - if (!$lockExpires && !$verifyIdentityResult) { - $this->expectException(\Magento\Framework\Exception\AuthenticationException::class); - $this->expectExceptionMessage( - (string)__('The password entered for the current user is invalid. Verify the password and try again.') - ); - } - - $this->model->performIdentityCheck($password); - } - - /** - * @return array - */ - public function dataProviderPerformIdentityCheck() - { - return [ - ['verifyIdentityResult' => true, 'lockExpires' => false], - ['verifyIdentityResult' => false, 'lockExpires' => false], - ['verifyIdentityResult' => true, 'lockExpires' => true], - ['verifyIdentityResult' => false, 'lockExpires' => true] - ]; - } } diff --git a/app/code/Magento/User/etc/config.xml b/app/code/Magento/User/etc/config.xml index f6a3924b5a27d..c1f51bcbecef4 100644 --- a/app/code/Magento/User/etc/config.xml +++ b/app/code/Magento/User/etc/config.xml @@ -10,6 +10,7 @@ <admin> <emails> <forgot_email_template>admin_emails_forgot_email_template</forgot_email_template> + <new_user_notification_template>admin_emails_new_user_notification_template</new_user_notification_template> <forgot_email_identity>general</forgot_email_identity> <user_notification_template>admin_emails_user_notification_template</user_notification_template> </emails> diff --git a/app/code/Magento/User/etc/di.xml b/app/code/Magento/User/etc/di.xml index b92549b54dac4..8fc85bc19e59a 100644 --- a/app/code/Magento/User/etc/di.xml +++ b/app/code/Magento/User/etc/di.xml @@ -17,4 +17,5 @@ </argument> </arguments> </type> + <preference for="Magento\User\Model\Spi\NotificatorInterface" type="Magento\User\Model\Notificator" /> </config> diff --git a/app/code/Magento/User/etc/email_templates.xml b/app/code/Magento/User/etc/email_templates.xml index b998f304c249b..637c2b799f37a 100644 --- a/app/code/Magento/User/etc/email_templates.xml +++ b/app/code/Magento/User/etc/email_templates.xml @@ -8,4 +8,10 @@ <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Email:etc/email_templates.xsd"> <template id="admin_emails_forgot_email_template" label="Forgot Admin Password" file="password_reset_confirmation.html" type="text" module="Magento_User" area="adminhtml"/> <template id="admin_emails_user_notification_template" label="User Notification" file="user_notification.html" type="text" module="Magento_User" area="adminhtml"/> + <template id="admin_emails_new_user_notification_template" + label="New User Notification" + file="new_user_notification.html" + type="text" + module="Magento_User" + area="adminhtml"/> </config> diff --git a/app/code/Magento/User/view/adminhtml/email/new_user_notification.html b/app/code/Magento/User/view/adminhtml/email/new_user_notification.html new file mode 100644 index 0000000000000..891faf5fb8c2b --- /dev/null +++ b/app/code/Magento/User/view/adminhtml/email/new_user_notification.html @@ -0,0 +1,18 @@ +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<!--@subject {{trans "New admin user '%user_name' created" user_name=$user.name}} @--> +<!--@vars { +"var store.getFrontendName()|escape":"Store Name" +} @--> + +{{trans "Hello,"}} + +{{trans "A new admin account was created for %first_name, %last_name using %email." first_name=$user.first_name last_name=$user.last_name email=$user.email}} +{{trans "If you have not authorized this action, please contact us immediately at %store_email" store_email=$store_email |escape}}{{depend store_phone}} {{trans "or call us at %store_phone" store_phone=$store_phone |escape}}{{/depend}}. + +{{trans "Thanks,"}} +{{var store.getFrontendName()}} diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php index d813e94437326..45b3056eac68d 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php @@ -11,6 +11,9 @@ */ namespace Magento\Widget\Block\Adminhtml\Widget; +/** + * Chooser widget block. + */ class Chooser extends \Magento\Backend\Block\Template { /** @@ -180,7 +183,7 @@ protected function _toHtml() <label class="widget-option-label" id="' . $chooserId . 'label">' . - ($this->getLabel() ? $this->getLabel() : __( + ($this->getLabel() ? $this->escapeHtml($this->getLabel()) : __( 'Not Selected' )) . '</label> diff --git a/app/design/frontend/Magento/blank/web/images/logo.svg b/app/design/frontend/Magento/blank/web/images/logo.svg index e4f627809b627..0f29d4e3eef21 100644 --- a/app/design/frontend/Magento/blank/web/images/logo.svg +++ b/app/design/frontend/Magento/blank/web/images/logo.svg @@ -1 +1 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 179.07329 60.13148"><defs><style>.a{fill:#f26322;}.b{fill:#4d4d4d;}</style></defs><title>Magento-an-Adobe-Company-logo-horizontal + diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml index 387e6418ee572..2cb0e2874040b 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutOfflinePaymentMethodsTest.xml @@ -125,7 +125,6 @@ - MAGETWO-66737: Magento\Checkout\Test\TestCase\OnePageCheckoutTest with OnePageCheckoutTestVariation3 and 4 is not stable test_type:acceptance_test, test_type:extended_acceptance_test, severity:S0 diff --git a/dev/tests/functional/utils/log.php b/dev/tests/functional/utils/log.php index b6c19458b10b9..68a68d4bad648 100644 --- a/dev/tests/functional/utils/log.php +++ b/dev/tests/functional/utils/log.php @@ -4,11 +4,16 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + if (!isset($_GET['name'])) { - throw new \InvalidArgumentException('The name of log file is required for getting logs.'); + throw new \InvalidArgumentException( + 'The name of log file is required for getting logs.' + ); } - $name = urldecode($_GET['name']); -$file = file_get_contents('../../../../var/log/' . $name); +if (preg_match('/\.\.(\\\|\/)/', $name)) { + throw new \InvalidArgumentException('Invalid log file name'); +} -echo serialize($file); +echo serialize(file_get_contents('../../../../var/log' .'/' .$name)); diff --git a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php index 1a726c4e2c174..5f4964d042da4 100644 --- a/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php +++ b/dev/tests/integration/testsuite/Magento/User/Controller/Adminhtml/AuthTest.php @@ -5,6 +5,9 @@ */ namespace Magento\User\Controller\Adminhtml; +use Magento\TestFramework\Mail\Template\TransportBuilderMock; +use Magento\TestFramework\Helper\Bootstrap; + /** * Test class for \Magento\User\Controller\Adminhtml\Auth * @@ -35,7 +38,7 @@ public function testForgotpasswordAction() $this->dispatch('backend/admin/auth/forgotpassword'); $this->assertRedirect( $this->equalTo( - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + Bootstrap::getObjectManager()->get( \Magento\Backend\Helper\Data::class )->getHomePageUrl() ) @@ -51,22 +54,25 @@ public function testForgotpasswordAction() */ public function testEmailSendForgotPasswordAction() { - $transportBuilderMock = $this->prepareEmailMock( - 1, - 'admin_emails_forgot_email_template', - 'general' + /** @var TransportBuilderMock $transportMock */ + $transportMock = Bootstrap::getObjectManager()->get( + TransportBuilderMock::class ); - $this->addMockToClass($transportBuilderMock, \Magento\User\Model\User::class); - $this->getRequest()->setPostValue('email', 'adminUser@example.com'); $this->dispatch('backend/admin/auth/forgotpassword'); $this->assertRedirect( $this->equalTo( - \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + Bootstrap::getObjectManager()->get( \Magento\Backend\Helper\Data::class )->getHomePageUrl() ) ); + $message = $transportMock->getSentMessage(); + $this->assertNotEmpty($message); + $this->assertEquals( + __('Password Reset Confirmation for %1', ['John Doe'])->render(), + $message->getSubject() + ); } /** @@ -79,13 +85,13 @@ public function testEmailSendForgotPasswordAction() public function testResetPasswordAction() { /** @var $user \Magento\User\Model\User */ - $user = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + $user = Bootstrap::getObjectManager()->create( \Magento\User\Model\User::class )->loadByUsername( 'dummy_username' ); $this->assertNotEmpty($user->getId(), 'Broken fixture'); - $resetPasswordToken = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get( + $resetPasswordToken = Bootstrap::getObjectManager()->get( \Magento\User\Helper\Data::class )->generateResetPasswordLinkToken(); $user->changeResetPasswordLinkToken($resetPasswordToken); @@ -123,7 +129,7 @@ public function testResetPasswordActionWithDummyToken() */ public function testResetPasswordPostAction($password, $passwordConfirmation, $isPasswordChanged) { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager = Bootstrap::getObjectManager(); /** @var $user \Magento\User\Model\User */ $user = $objectManager->create(\Magento\User\Model\User::class); @@ -203,7 +209,7 @@ public function testResetPasswordPostActionWithDummyToken() \Magento\Framework\Message\MessageInterface::TYPE_ERROR ); - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager = Bootstrap::getObjectManager(); /** @var \Magento\Backend\Helper\Data $backendHelper */ $backendHelper = $objectManager->get(\Magento\Backend\Helper\Data::class); @@ -218,7 +224,7 @@ public function testResetPasswordPostActionWithDummyToken() */ public function testResetPasswordPostActionWithInvalidPassword() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $objectManager = Bootstrap::getObjectManager(); $user = $objectManager->create(\Magento\User\Model\User::class); $user->loadByUsername('dummy_username'); diff --git a/lib/web/images/logo.svg b/lib/web/images/logo.svg index e4f627809b627..0f29d4e3eef21 100644 --- a/lib/web/images/logo.svg +++ b/lib/web/images/logo.svg @@ -1 +1 @@ -Magento-an-Adobe-Company-logo-horizontal + diff --git a/lib/web/images/magento-logo.svg b/lib/web/images/magento-logo.svg index e4f627809b627..0f29d4e3eef21 100644 --- a/lib/web/images/magento-logo.svg +++ b/lib/web/images/magento-logo.svg @@ -1 +1 @@ -Magento-an-Adobe-Company-logo-horizontal + diff --git a/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php b/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php index 00fa272e74962..173ea9e49a8a4 100644 --- a/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php +++ b/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php @@ -15,6 +15,9 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Question\Question; +/** + * Command to create an admin user. + */ class AdminUserCreateCommand extends AbstractSetupCommand { /** @@ -52,6 +55,8 @@ protected function configure() } /** + * Creation admin user in interaction mode. + * * @param \Symfony\Component\Console\Input\InputInterface $input * @param \Symfony\Component\Console\Output\OutputInterface $output * @@ -129,6 +134,8 @@ protected function interact(InputInterface $input, OutputInterface $output) } /** + * Add not empty validator. + * * @param \Symfony\Component\Console\Question\Question $question * @return void */ @@ -144,7 +151,7 @@ private function addNotEmptyValidator(Question $question) } /** - * {@inheritdoc} + * @inheritdoc */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -165,25 +172,43 @@ protected function execute(InputInterface $input, OutputInterface $output) /** * Get list of arguments for the command * + * @param int $mode The mode of options. * @return InputOption[] */ - public function getOptionsList() + public function getOptionsList($mode = InputOption::VALUE_REQUIRED) { + $requiredStr = ($mode === InputOption::VALUE_REQUIRED ? '(Required) ' : ''); + return [ - new InputOption(AdminAccount::KEY_USER, null, InputOption::VALUE_REQUIRED, '(Required) Admin user'), - new InputOption(AdminAccount::KEY_PASSWORD, null, InputOption::VALUE_REQUIRED, '(Required) Admin password'), - new InputOption(AdminAccount::KEY_EMAIL, null, InputOption::VALUE_REQUIRED, '(Required) Admin email'), + new InputOption( + AdminAccount::KEY_USER, + null, + $mode, + $requiredStr . 'Admin user' + ), + new InputOption( + AdminAccount::KEY_PASSWORD, + null, + $mode, + $requiredStr . 'Admin password' + ), + new InputOption( + AdminAccount::KEY_EMAIL, + null, + $mode, + $requiredStr . 'Admin email' + ), new InputOption( AdminAccount::KEY_FIRST_NAME, null, - InputOption::VALUE_REQUIRED, - '(Required) Admin first name' + $mode, + $requiredStr . 'Admin first name' ), new InputOption( AdminAccount::KEY_LAST_NAME, null, - InputOption::VALUE_REQUIRED, - '(Required) Admin last name' + $mode, + $requiredStr . 'Admin last name' ), ]; } diff --git a/setup/src/Magento/Setup/Console/Command/InstallCommand.php b/setup/src/Magento/Setup/Console/Command/InstallCommand.php index 7e767292b36e8..74c2e3b24234c 100644 --- a/setup/src/Magento/Setup/Console/Command/InstallCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InstallCommand.php @@ -10,6 +10,8 @@ use Magento\Deploy\Console\Command\App\ConfigImportCommand; use Magento\Framework\Setup\Declaration\Schema\DryRunLogger; use Magento\Framework\Setup\Declaration\Schema\OperationsExecutor; +use Magento\Framework\Setup\Declaration\Schema\Request; +use Magento\Setup\Model\AdminAccount; use Magento\Setup\Model\ConfigModel; use Magento\Setup\Model\InstallerFactory; use Magento\Framework\Setup\ConsoleLogger; @@ -136,7 +138,7 @@ protected function configure() { $inputOptions = $this->configModel->getAvailableOptions(); $inputOptions = array_merge($inputOptions, $this->userConfig->getOptionsList()); - $inputOptions = array_merge($inputOptions, $this->adminUser->getOptionsList()); + $inputOptions = array_merge($inputOptions, $this->adminUser->getOptionsList(InputOption::VALUE_OPTIONAL)); $inputOptions = array_merge($inputOptions, [ new InputOption( self::INPUT_KEY_CLEANUP_DB, @@ -251,7 +253,7 @@ protected function initialize(InputInterface $input, OutputInterface $output) } $errors = $this->configModel->validate($configOptionsToValidate); - $errors = array_merge($errors, $this->adminUser->validate($input)); + $errors = array_merge($errors, $this->validateAdmin($input)); $errors = array_merge($errors, $this->validate($input)); $errors = array_merge($errors, $this->userConfig->validate($input)); @@ -320,7 +322,7 @@ private function interactiveQuestions(InputInterface $input, OutputInterface $ou $output->writeln(""); - foreach ($this->adminUser->getOptionsList() as $option) { + foreach ($this->adminUser->getOptionsList(InputOption::VALUE_OPTIONAL) as $option) { $configOptionsToValidate[$option->getName()] = $this->askQuestion( $input, $output, @@ -411,4 +413,24 @@ private function askQuestion( return $value; } + + /** + * Performs validation of admin options if at least one of them was set. + * + * @param InputInterface $input + * @return array + */ + private function validateAdmin(InputInterface $input): array + { + if ($input->getOption(AdminAccount::KEY_FIRST_NAME) + || $input->getOption(AdminAccount::KEY_LAST_NAME) + || $input->getOption(AdminAccount::KEY_EMAIL) + || $input->getOption(AdminAccount::KEY_USER) + || $input->getOption(AdminAccount::KEY_PASSWORD) + ) { + return $this->adminUser->validate($input); + } + + return []; + } } diff --git a/setup/src/Magento/Setup/Model/Installer.php b/setup/src/Magento/Setup/Model/Installer.php index c7a84b0cf022e..ad24307ac18b9 100644 --- a/setup/src/Magento/Setup/Model/Installer.php +++ b/setup/src/Magento/Setup/Model/Installer.php @@ -267,7 +267,7 @@ class Installer * @param \Magento\Framework\Setup\SampleData\State $sampleDataState * @param ComponentRegistrar $componentRegistrar * @param PhpReadinessCheck $phpReadinessCheck - * + * @throws \Magento\Setup\Exception * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -345,7 +345,9 @@ public function install($request) [$request[InstallCommand::INPUT_KEY_SALES_ORDER_INCREMENT_PREFIX]], ]; } - $script[] = ['Installing admin user...', 'installAdminUser', [$request]]; + if ($this->isAdminDataSet($request)) { + $script[] = ['Installing admin user...', 'installAdminUser', [$request]]; + } if (!$this->isDryRun($request)) { $script[] = ['Caches clearing:', 'cleanCaches', [$request]]; @@ -909,6 +911,7 @@ private function throwExceptionForNotWritablePaths(array $paths) * @param string $type * @param array $request * @return void + * @throws \Magento\Framework\Setup\Exception * @throws \Magento\Setup\Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -1473,4 +1476,28 @@ private function cleanupGeneratedFiles() $this->log->log($message); } } + + /** + * Checks that admin data is not empty in request array + * + * @param \ArrayObject|array $request + * @return bool + */ + private function isAdminDataSet($request) + { + $adminData = array_filter($request, function ($value, $key) { + return in_array( + $key, + [ + AdminAccount::KEY_EMAIL, + AdminAccount::KEY_FIRST_NAME, + AdminAccount::KEY_LAST_NAME, + AdminAccount::KEY_USER, + AdminAccount::KEY_PASSWORD, + ] + ) && $value !== null; + }, ARRAY_FILTER_USE_BOTH); + + return !empty($adminData); + } } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php index a80302b40a617..b90fa66b3d450 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php @@ -11,8 +11,12 @@ use Magento\User\Model\UserValidationRules; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\QuestionHelper; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Tester\CommandTester; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class AdminUserCreateCommandTest extends \PHPUnit\Framework\TestCase { /** @@ -125,11 +129,34 @@ public function testInteraction() ); } - public function testGetOptionsList() + /** + * @param int $mode + * @param string $description + * @dataProvider getOptionListDataProvider + */ + public function testGetOptionsList($mode, $description) { /* @var $argsList \Symfony\Component\Console\Input\InputArgument[] */ - $argsList = $this->command->getOptionsList(); + $argsList = $this->command->getOptionsList($mode); $this->assertEquals(AdminAccount::KEY_EMAIL, $argsList[2]->getName()); + $this->assertEquals($description, $argsList[2]->getDescription()); + } + + /** + * @return array + */ + public function getOptionListDataProvider() + { + return [ + [ + 'mode' => InputOption::VALUE_REQUIRED, + 'description' => '(Required) Admin email', + ], + [ + 'mode' => InputOption::VALUE_OPTIONAL, + 'description' => 'Admin email', + ], + ]; } /** diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php index 5b7b6c1626911..3c3a875a278e8 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallCommandTest.php @@ -16,6 +16,7 @@ use Magento\Backend\Setup\ConfigOptionsList as BackendConfigOptionsList; use Magento\Framework\Config\ConfigOptionsListConstants as SetupConfigOptionsList; use Magento\Setup\Model\StoreConfigurationDataMapper; +use Magento\Setup\Console\Command\AdminUserCreateCommand; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -62,6 +63,11 @@ class InstallCommandTest extends \PHPUnit\Framework\TestCase */ private $configImportMock; + /** + * @var AdminUserCreateCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $adminUserMock; + public function setUp() { $this->input = [ @@ -73,11 +79,6 @@ public function setUp() '--' . StoreConfigurationDataMapper::KEY_LANGUAGE => 'en_US', '--' . StoreConfigurationDataMapper::KEY_TIMEZONE => 'America/Chicago', '--' . StoreConfigurationDataMapper::KEY_CURRENCY => 'USD', - '--' . AdminAccount::KEY_USER => 'user', - '--' . AdminAccount::KEY_PASSWORD => '123123q', - '--' . AdminAccount::KEY_EMAIL => 'test@test.com', - '--' . AdminAccount::KEY_FIRST_NAME => 'John', - '--' . AdminAccount::KEY_LAST_NAME => 'Doe', ]; $configModel = $this->createMock(\Magento\Setup\Model\ConfigModel::class); @@ -100,15 +101,11 @@ public function setUp() ->method('validate') ->will($this->returnValue([])); - $adminUser = $this->createMock(\Magento\Setup\Console\Command\AdminUserCreateCommand::class); - $adminUser + $this->adminUserMock = $this->createMock(AdminUserCreateCommand::class); + $this->adminUserMock ->expects($this->once()) ->method('getOptionsList') ->will($this->returnValue($this->getOptionsListAdminUser())); - $adminUser - ->expects($this->once()) - ->method('validate') - ->will($this->returnValue([])); $this->installerFactory = $this->createMock(\Magento\Setup\Model\InstallerFactory::class); $this->installer = $this->createMock(\Magento\Setup\Model\Installer::class); @@ -143,7 +140,7 @@ public function setUp() $this->installerFactory, $configModel, $userConfig, - $adminUser + $this->adminUserMock ); $this->command->setApplication( $this->applicationMock @@ -152,6 +149,16 @@ public function setUp() public function testExecute() { + $this->input['--' . AdminAccount::KEY_USER] = 'user'; + $this->input['--' . AdminAccount::KEY_PASSWORD] = '123123q'; + $this->input['--' . AdminAccount::KEY_EMAIL] = 'test@test.com'; + $this->input['--' . AdminAccount::KEY_FIRST_NAME] = 'John'; + $this->input['--' . AdminAccount::KEY_LAST_NAME] = 'Doe'; + + $this->adminUserMock + ->expects($this->once()) + ->method('validate') + ->willReturn([]); $this->installerFactory->expects($this->once()) ->method('create') ->will($this->returnValue($this->installer)); @@ -269,6 +276,9 @@ private function getOptionsListAdminUser() */ public function testValidate($prefixValue) { + $this->adminUserMock + ->expects($this->never()) + ->method('validate'); $this->installerFactory->expects($this->once()) ->method('create') ->will($this->returnValue($this->installer)); @@ -288,6 +298,9 @@ public function testValidate($prefixValue) */ public function testValidateWithException($prefixValue) { + $this->adminUserMock + ->expects($this->never()) + ->method('validate'); $this->installerFactory->expects($this->never()) ->method('create') ->will($this->returnValue($this->installer)); diff --git a/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php b/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php index 8a4263be18ecf..e600002d53560 100644 --- a/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Model/InstallerTest.php @@ -9,6 +9,7 @@ use Magento\Backend\Setup\ConfigOptionsList; use Magento\Framework\Config\ConfigOptionsListConstants; use Magento\Framework\Setup\SchemaListener; + use Magento\Setup\Model\AdminAccount; use Magento\Setup\Model\DeclarationInstaller; use Magento\Setup\Model\Installer; use Magento\Framework\App\Filesystem\DirectoryList; @@ -268,17 +269,13 @@ private function createObject($connectionFactory = false, $objectManagerProvider } /** + * @param array $request + * @param array $logMessages + * @dataProvider installDataProvider * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testInstall() + public function testInstall(array $request, array $logMessages) { - $request = [ - ConfigOptionsListConstants::INPUT_KEY_DB_HOST => '127.0.0.1', - ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'magento', - ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'magento', - ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => 'encryption_key', - ConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME => 'backend', - ]; $this->config->expects($this->atLeastOnce()) ->method('get') ->willReturnMap( @@ -353,7 +350,7 @@ public function testInstall() [\Magento\Setup\Model\DeclarationInstaller::class, $this->declarationInstallerMock], [\Magento\Framework\Registry::class, $registry] ])); - $this->adminFactory->expects($this->once())->method('create')->willReturn( + $this->adminFactory->expects($this->any())->method('create')->willReturn( $this->createMock(\Magento\Setup\Model\AdminAccount::class) ); $this->sampleDataState->expects($this->once())->method('hasError')->willReturn(true); @@ -366,11 +363,119 @@ public function testInstall() $this->filePermissions->expects($this->once()) ->method('getMissingWritableDirectoriesForDbUpgrade') ->willReturn([]); - $this->setupLoggerExpectsForInstall(); + call_user_func_array( + [ + $this->logger->expects($this->exactly(count($logMessages)))->method('log'), + 'withConsecutive' + ], + $logMessages + ); + $this->logger->expects($this->exactly(2)) + ->method('logSuccess') + ->withConsecutive( + ['Magento installation complete.'], + ['Magento Admin URI: /'] + ); $this->object->install($request); } + /** + * @return array + */ + public function installDataProvider() + { + return [ + [ + 'request' => [ + ConfigOptionsListConstants::INPUT_KEY_DB_HOST => '127.0.0.1', + ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'magento', + ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'magento', + ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => 'encryption_key', + ConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME => 'backend', + ], + 'logMessages' => [ + ['Starting Magento installation:'], + ['File permissions check...'], + ['Required extensions check...'], + ['Enabling Maintenance Mode...'], + ['Installing deployment configuration...'], + ['Installing database schema:'], + ['Schema creation/updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Schema post-updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Installing user configuration...'], + ['Enabling caches:'], + ['Current status:'], + [''], + ['Installing data...'], + ['Data install/update:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Data post-updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + //['Installing admin user...'], + ['Caches clearing:'], + ['Cache cleared successfully'], + ['Disabling Maintenance Mode:'], + ['Post installation file permissions check...'], + ['Write installation date...'], + ['Sample Data is installed with errors. See log file for details'] + ], + ], + [ + 'request' => [ + ConfigOptionsListConstants::INPUT_KEY_DB_HOST => '127.0.0.1', + ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'magento', + ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'magento', + ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => 'encryption_key', + ConfigOptionsList::INPUT_KEY_BACKEND_FRONTNAME => 'backend', + AdminAccount::KEY_USER => 'admin', + AdminAccount::KEY_PASSWORD => '123', + AdminAccount::KEY_EMAIL => 'admin@example.com', + AdminAccount::KEY_FIRST_NAME => 'John', + AdminAccount::KEY_LAST_NAME => 'Doe', + ], + 'logMessages' => [ + ['Starting Magento installation:'], + ['File permissions check...'], + ['Required extensions check...'], + ['Enabling Maintenance Mode...'], + ['Installing deployment configuration...'], + ['Installing database schema:'], + ['Schema creation/updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Schema post-updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Installing user configuration...'], + ['Enabling caches:'], + ['Current status:'], + [''], + ['Installing data...'], + ['Data install/update:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Data post-updates:'], + ['Module \'Foo_One\':'], + ['Module \'Bar_Two\':'], + ['Installing admin user...'], + ['Caches clearing:'], + ['Cache cleared successfully'], + ['Disabling Maintenance Mode:'], + ['Post installation file permissions check...'], + ['Write installation date...'], + ['Sample Data is installed with errors. See log file for details'] + ], + ], + ]; + } + public function testCheckInstallationFilePermissions() { $this->filePermissions @@ -590,44 +695,6 @@ private function prepareForUpdateModulesTests() return $newObject; } - - /** - * Set up logger expectations for install method - * @return void - */ - private function setupLoggerExpectsForInstall() - { - $this->logger->expects($this->at(0))->method('log')->with('Starting Magento installation:'); - $this->logger->expects($this->at(1))->method('log')->with('File permissions check...'); - $this->logger->expects($this->at(3))->method('log')->with('Required extensions check...'); - // at(2) invokes logMeta() - $this->logger->expects($this->at(5))->method('log')->with('Enabling Maintenance Mode...'); - // at(4) - logMeta and so on... - $this->logger->expects($this->at(7))->method('log')->with('Installing deployment configuration...'); - $this->logger->expects($this->at(9))->method('log')->with('Installing database schema:'); - $this->logger->expects($this->at(11))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(13))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(15))->method('log')->with('Schema post-updates:'); - $this->logger->expects($this->at(16))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(18))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(21))->method('log')->with('Installing user configuration...'); - $this->logger->expects($this->at(23))->method('log')->with('Enabling caches:'); - $this->logger->expects($this->at(27))->method('log')->with('Installing data...'); - $this->logger->expects($this->at(28))->method('log')->with('Data install/update:'); - $this->logger->expects($this->at(29))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(31))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(33))->method('log')->with('Data post-updates:'); - $this->logger->expects($this->at(34))->method('log')->with("Module 'Foo_One':"); - $this->logger->expects($this->at(36))->method('log')->with("Module 'Bar_Two':"); - $this->logger->expects($this->at(39))->method('log')->with('Installing admin user...'); - $this->logger->expects($this->at(41))->method('log')->with('Caches clearing:'); - $this->logger->expects($this->at(44))->method('log')->with('Disabling Maintenance Mode:'); - $this->logger->expects($this->at(46))->method('log')->with('Post installation file permissions check...'); - $this->logger->expects($this->at(48))->method('log')->with('Write installation date...'); - $this->logger->expects($this->at(50))->method('logSuccess')->with('Magento installation complete.'); - $this->logger->expects($this->at(52))->method('log') - ->with('Sample Data is installed with errors. See log file for details'); - } } } diff --git a/setup/view/magento/setup/marketplace-credentials.phtml b/setup/view/magento/setup/marketplace-credentials.phtml index 32ae512313610..8bd0bb0438d61 100644 --- a/setup/view/magento/setup/marketplace-credentials.phtml +++ b/setup/view/magento/setup/marketplace-credentials.phtml @@ -7,7 +7,7 @@