diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 4a72539e982f25591d21818954087a8413c7bb3f..4927a191467239c5c3fdaff033856e4e877de8c7 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -312,7 +312,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa if ($this->cacheLimit && count($this->instances) > $this->cacheLimit) { $offset = round($this->cacheLimit / -2); $this->instancesById = array_slice($this->instancesById, $offset, null, true); - $this->instances = array_slice($this->instances, $offset); + $this->instances = array_slice($this->instances, $offset, null, true); } } diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index 7847098083949baa26b81e408c641f0150b23ecc..9e021a21d23b348ea087160afd8962f65ed6590c 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -395,6 +395,10 @@ class Subscriber extends \Magento\Framework\Model\AbstractModel { $this->loadByEmail($email); + if ($this->getId() && $this->getStatus() == self::STATUS_SUBSCRIBED) { + return $this->getStatus(); + } + if (!$this->getId()) { $this->setSubscriberConfirmCode($this->randomSequence()); } diff --git a/app/code/Magento/Quote/Model/Quote/Address.php b/app/code/Magento/Quote/Model/Quote/Address.php index 109ca91cb2ae81ca259665b0d8a39f4968f78026..87c5feaba8f2eef4f140b62f04e7ba294493ca55 100644 --- a/app/code/Magento/Quote/Model/Quote/Address.php +++ b/app/code/Magento/Quote/Model/Quote/Address.php @@ -1170,7 +1170,8 @@ class Address extends \Magento\Customer\Model\Address\AbstractAddress implements */ public function getAppliedTaxes() { - return $this->serializer->unserialize($this->getData('applied_taxes')); + $taxes = $this->getData('applied_taxes'); + return $taxes ? $this->serializer->unserialize($taxes) : []; } /** diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php index 1557fe420be025c1b003e0477c5737a0c424f0c8..d01ae7304bdc643c90f14844dcc79fa1b6c4b473 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/AddressTest.php @@ -9,7 +9,7 @@ namespace Magento\Quote\Test\Unit\Model\Quote; use Magento\Directory\Model\Currency; -use \Magento\Quote\Model\Quote\Address; +use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Address\Rate; use Magento\Quote\Model\ResourceModel\Quote\Address\Rate\CollectionFactory as RateCollectionFactory; use Magento\Quote\Model\ResourceModel\Quote\Address\Rate\Collection as RatesCollection; @@ -28,6 +28,7 @@ use Magento\Store\Model\StoreManagerInterface; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Api\Data\WebsiteInterface; use Magento\Quote\Model\Quote\Address\RateResult\AbstractResult; +use Magento\Framework\Serialize\Serializer\Json; /** * Test class for sales quote address model @@ -117,7 +118,7 @@ class AddressTest extends \PHPUnit\Framework\TestCase $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->scopeConfig = $this->createMock(\Magento\Framework\App\Config::class); - $this->serializer = $this->createMock(\Magento\Framework\Serialize\Serializer\Json::class); + $this->serializer = new Json(); $this->requestFactory = $this->getMockBuilder(RateRequestFactory::class) ->disableOriginalConstructor() @@ -273,20 +274,17 @@ class AddressTest extends \PHPUnit\Framework\TestCase public function testSetAndGetAppliedTaxes() { $data = ['data']; - $result = json_encode($data); - - $this->serializer->expects($this->once()) - ->method('serialize') - ->with($data) - ->willReturn($result); - - $this->serializer->expects($this->once()) - ->method('unserialize') - ->with($result) - ->willReturn($data); + self::assertInstanceOf(Address::class, $this->address->setAppliedTaxes($data)); + self::assertEquals($data, $this->address->getAppliedTaxes()); + } - $this->assertInstanceOf(\Magento\Quote\Model\Quote\Address::class, $this->address->setAppliedTaxes($data)); - $this->assertEquals($data, $this->address->getAppliedTaxes()); + /** + * Checks a case, when applied taxes are not provided. + */ + public function testGetAppliedTaxesWithEmptyValue() + { + $this->address->setData('applied_taxes', null); + self::assertEquals([], $this->address->getAppliedTaxes()); } /** diff --git a/app/code/Magento/Reports/Model/ResourceModel/Review/Customer/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Review/Customer/Collection.php index a83282396764766773edb8b00731c81a9072ad1d..9297a1dda18d8c522fde78e75c7c63d8145581f4 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Review/Customer/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Review/Customer/Collection.php @@ -110,13 +110,14 @@ class Collection extends \Magento\Review\Model\ResourceModel\Review\Collection */ public function getSelectCountSql() { - $countSelect = clone $this->_select; + $countSelect = clone $this->getSelect(); $countSelect->reset(\Magento\Framework\DB\Select::ORDER); $countSelect->reset(\Magento\Framework\DB\Select::GROUP); $countSelect->reset(\Magento\Framework\DB\Select::HAVING); $countSelect->reset(\Magento\Framework\DB\Select::LIMIT_COUNT); $countSelect->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET); $countSelect->reset(\Magento\Framework\DB\Select::COLUMNS); + $countSelect->reset(\Magento\Framework\DB\Select::WHERE); $countSelect->columns(new \Zend_Db_Expr('COUNT(DISTINCT detail.customer_id)')); diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index ccc5c134514f64c8f5dca36bfdb93d571f002b88..c8afc6d8a292b9b79173c80bc4bc9eb003abdf39 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -10,6 +10,8 @@ namespace Magento\Sales\Model\AdminOrder; use Magento\Customer\Api\AddressMetadataInterface; use Magento\Customer\Model\Metadata\Form as CustomerForm; +use Magento\Framework\App\ObjectManager; +use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Item; /** @@ -323,7 +325,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ $this->dataObjectHelper = $dataObjectHelper; $this->orderManagement = $orderManagement; $this->quoteFactory = $quoteFactory; - $this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance() + $this->serializer = $serializer ?: ObjectManager::getInstance() ->get(\Magento\Framework\Serialize\Serializer\Json::class); parent::__construct($data); } @@ -1449,32 +1451,36 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ */ public function setBillingAddress($address) { - if (is_array($address)) { - $billingAddress = $this->_objectManager->create( - \Magento\Quote\Model\Quote\Address::class - )->setData( - $address - )->setAddressType( - \Magento\Quote\Model\Quote\Address::TYPE_BILLING - ); - $this->_setQuoteAddress($billingAddress, $address); - /** - * save_in_address_book is not a valid attribute and is filtered out by _setQuoteAddress, - * that is why it should be added after _setQuoteAddress call - */ - $saveInAddressBook = (int)(!empty($address['save_in_address_book'])); - $billingAddress->setData('save_in_address_book', $saveInAddressBook); - - if (!$this->getQuote()->isVirtual() && $this->getShippingAddress()->getSameAsBilling()) { - $shippingAddress = clone $billingAddress; - $shippingAddress->setSameAsBilling(true); - $shippingAddress->setSaveInAddressBook(false); - $address['save_in_address_book'] = 0; - $this->setShippingAddress($address); - } + if (!is_array($address)) { + return $this; + } + + $billingAddress = $this->_objectManager->create(Address::class) + ->setData($address) + ->setAddressType(Address::TYPE_BILLING); + + $this->_setQuoteAddress($billingAddress, $address); + + /** + * save_in_address_book is not a valid attribute and is filtered out by _setQuoteAddress, + * that is why it should be added after _setQuoteAddress call + */ + $saveInAddressBook = (int)(!empty($address['save_in_address_book'])); + $billingAddress->setData('save_in_address_book', $saveInAddressBook); + + $quote = $this->getQuote(); + if (!$quote->isVirtual() && $this->getShippingAddress()->getSameAsBilling()) { + $address['save_in_address_book'] = 0; + $this->setShippingAddress($address); + } - $this->getQuote()->setBillingAddress($billingAddress); + // not assigned billing address should be saved as new + // but if quote already has the billing address it won't be overridden + if (empty($billingAddress->getCustomerAddressId())) { + $billingAddress->setCustomerAddressId(null); + $quote->getBillingAddress()->setCustomerAddressId(null); } + $quote->setBillingAddress($billingAddress); return $this; } @@ -1775,6 +1781,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ $address = $this->getShippingAddress()->setCustomerId($this->getQuote()->getCustomer()->getId()); $this->setShippingAddress($address); } + $this->getBillingAddress()->setCustomerId($customer->getId()); $this->getQuote()->updateCustomerData($this->getQuote()->getCustomer()); $customer = $this->getQuote()->getCustomer(); diff --git a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js index 1c755b4e08a0d730e0fbc0bf17587c0869ea684f..8e25584627d3b16bbbfa0b42436d13a3a1855d66 100644 --- a/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js +++ b/app/code/Magento/Sales/view/adminhtml/web/order/create/scripts.js @@ -157,6 +157,7 @@ define([ } if(this.addresses[id]){ this.fillAddressFields(container, this.addresses[id]); + } else{ this.fillAddressFields(container, {}); @@ -190,43 +191,53 @@ define([ } }, - changeAddressField : function(event){ - var field = Event.element(event); - var re = /[^\[]*\[([^\]]*)_address\]\[([^\]]*)\](\[(\d)\])?/; - var matchRes = field.name.match(re); + /** + * Triggers on each form's element changes. + * + * @param {Object} event + */ + changeAddressField: function (event) { + var field = Event.element(event), + re = /[^\[]*\[([^\]]*)_address\]\[([^\]]*)\](\[(\d)\])?/, + matchRes = field.name.match(re), + type, + name, + data; if (!matchRes) { return; } - var type = matchRes[1]; - var name = matchRes[2]; - var data; + type = matchRes[1]; + name = matchRes[2]; - if(this.isBillingField(field.id)){ - data = this.serializeData(this.billingAddressContainer) - } - else{ - data = this.serializeData(this.shippingAddressContainer) + if (this.isBillingField(field.id)) { + data = this.serializeData(this.billingAddressContainer); + } else { + data = this.serializeData(this.shippingAddressContainer); } data = data.toObject(); - if( (type == 'billing' && this.shippingAsBilling) - || (type == 'shipping' && !this.shippingAsBilling) ) { + if (type === 'billing' && this.shippingAsBilling || type === 'shipping' && !this.shippingAsBilling) { data['reset_shipping'] = true; } - data['order['+type+'_address][customer_address_id]'] = $('order-'+type+'_address_customer_address_id').value; + data['order[' + type + '_address][customer_address_id]'] = null; + data['shipping_as_billing'] = jQuery('[name="shipping_same_as_billing"]').is(':checked') ? 1 : 0; + + if (name === 'customer_address_id') { + data['order[' + type + '_address][customer_address_id]'] = + $('order-' + type + '_address_customer_address_id').value; + } if (data['reset_shipping']) { this.resetShippingMethod(data); } else { this.saveData(data); - if (name == 'country_id' || name == 'customer_address_id') { + + if (name === 'country_id' || name === 'customer_address_id') { this.loadArea(['shipping_method', 'billing_method', 'totals', 'items'], true, data); } - // added for reloading of default sender and default recipient for giftmessages - //this.loadArea(['giftmessage'], true, data); } }, diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/WebApiApplication.php b/dev/tests/api-functional/framework/Magento/TestFramework/WebApiApplication.php index 03364acbb86634b66c4bcddd8589a78c6d68c105..992653a3a65d6d3bb4cb4f4757781881e693be8c 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/WebApiApplication.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/WebApiApplication.php @@ -26,8 +26,12 @@ class WebApiApplication extends Application /** * {@inheritdoc} */ - public function install() + public function install($cleanup) { + if ($cleanup) { + $this->cleanup(); + } + $installOptions = $this->getInstallConfig(); /* Install application */ diff --git a/dev/tests/api-functional/framework/bootstrap.php b/dev/tests/api-functional/framework/bootstrap.php index 7a7b061858ac75a609d52c20bd6056801797b2c0..942582392e89e8bff1c99ddefedc523e676912df 100644 --- a/dev/tests/api-functional/framework/bootstrap.php +++ b/dev/tests/api-functional/framework/bootstrap.php @@ -58,10 +58,8 @@ try { ); if (defined('TESTS_MAGENTO_INSTALLATION') && TESTS_MAGENTO_INSTALLATION === 'enabled') { - if (defined('TESTS_CLEANUP') && TESTS_CLEANUP === 'enabled') { - $application->cleanup(); - } - $application->install(); + $cleanup = (defined('TESTS_CLEANUP') && TESTS_CLEANUP === 'enabled'); + $application->install($cleanup); } $bootstrap = new \Magento\TestFramework\Bootstrap( diff --git a/dev/tests/integration/framework/Magento/TestFramework/Application.php b/dev/tests/integration/framework/Magento/TestFramework/Application.php index 2f4395b161a69943786ad79c922702e537dd1107..e0cd3c52e2ca31f9ec6f051ff36cd4b9480e4ded 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Application.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Application.php @@ -435,10 +435,11 @@ class Application /** * Install an application * + * @param bool $cleanup * @return void * @throws \Magento\Framework\Exception\LocalizedException */ - public function install() + public function install($cleanup) { $dirs = \Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS; $this->_ensureDirExists($this->installDir); @@ -453,8 +454,9 @@ class Application $installParams = $this->getInstallCliParams(); // performance optimization: restore DB from last good dump to make installation on top of it (much faster) + // do not restore from the database if the cleanup option is set to ensure we have a clean DB to test on $db = $this->getDbInstance(); - if ($db->isDbDumpExists()) { + if ($db->isDbDumpExists() && !$cleanup) { $db->restoreFromDbDump(); } diff --git a/dev/tests/integration/framework/bootstrap.php b/dev/tests/integration/framework/bootstrap.php index 13008938147b56431223a3c44cd13ac484a8a189..4e14c8113a708bce1b86f81728a6d4745ab44c7f 100644 --- a/dev/tests/integration/framework/bootstrap.php +++ b/dev/tests/integration/framework/bootstrap.php @@ -74,7 +74,7 @@ try { $application->cleanup(); } if (!$application->isInstalled()) { - $application->install(); + $application->install($settings->getAsBoolean('TESTS_CLEANUP')); } $application->initialize([]); diff --git a/dev/tests/integration/testsuite/Magento/Reports/Model/ResourceModel/Review/Customer/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Reports/Model/ResourceModel/Review/Customer/CollectionTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2decae1acf9acf93426fce9c8adea30a9194a787 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Reports/Model/ResourceModel/Review/Customer/CollectionTest.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Reports\Model\ResourceModel\Review\Customer; + +/** + * @magentoAppArea adminhtml + */ +class CollectionTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \Magento\Reports\Model\ResourceModel\Review\Customer\Collection + */ + private $collection; + + protected function setUp() + { + $this->collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Reports\Model\ResourceModel\Review\Customer\Collection::class + ); + } + + /** + * This tests covers issue described in: + * https://github.com/magento/magento2/issues/10301 + * + * @magentoDataFixture Magento/Review/_files/customer_review.php + */ + public function testSelectCountSql() + { + $this->collection->addFieldToFilter('customer_name', ['like' => '%john%']); + $this->assertEquals(1, $this->collection->getSize()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CreateTest.php b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CreateTest.php index 5f3dbdabc640ab50c2283743cb02ba8801f17edc..e071dde26a263ec68a94f0b4b0df2241ac055403 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CreateTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Controller/Adminhtml/Order/CreateTest.php @@ -5,6 +5,10 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Backend\Model\Session\Quote; +use Magento\Quote\Api\CartRepositoryInterface; + /** * @magentoAppArea adminhtml * @magentoDbIsolation enabled @@ -158,7 +162,7 @@ class CreateTest extends \Magento\TestFramework\TestCase\AbstractBackendControll */ public function testGetAclResource($actionName, $reordered, $expectedResult) { - $this->_objectManager->get(\Magento\Backend\Model\Session\Quote::class)->setReordered($reordered); + $this->_objectManager->get(Quote::class)->setReordered($reordered); $orderController = $this->_objectManager->get( \Magento\Sales\Controller\Adminhtml\Order\Stub\OrderCreateStub::class ); @@ -229,4 +233,57 @@ class CreateTest extends \Magento\TestFramework\TestCase\AbstractBackendControll $this->dispatch('backend/sales/order_create/save'); $this->assertEquals('403', $this->getResponse()->getHttpResponseCode()); } + + /** + * Checks a case when shipping is the same as billing and billing address details was changed by request. + * Both billing and shipping addresses should be updated. + * + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Sales/_files/quote_with_customer.php + */ + public function testSyncBetweenQuoteAddresses() + { + /** @var CustomerRepositoryInterface $customerRepository */ + $customerRepository = $this->_objectManager->get(CustomerRepositoryInterface::class); + $customer = $customerRepository->get('customer@example.com'); + + /** @var CartRepositoryInterface $quoteRepository */ + $quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); + $quote = $quoteRepository->getActiveForCustomer($customer->getId()); + + $session = $this->_objectManager->get(Quote::class); + $session->setQuoteId($quote->getId()); + + $data = [ + 'firstname' => 'John', + 'lastname' => 'Doe', + 'street' => ['Soborna 23'], + 'city' => 'Kyiv', + 'country_id' => 'UA', + 'region' => 'Kyivska', + 'region_id' => 1 + ]; + $this->getRequest()->setPostValue( + [ + 'order' => ['billing_address' => $data], + 'reset_shipping' => 1, + 'customer_id' => $customer->getId(), + 'store_id' => 1, + 'json' => true + ] + ); + + $this->dispatch('backend/sales/order_create/loadBlock/block/shipping_address'); + self::assertEquals(200, $this->getResponse()->getHttpResponseCode()); + + $updatedQuote = $quoteRepository->get($quote->getId()); + + $billingAddress = $updatedQuote->getBillingAddress(); + self::assertEquals($data['region_id'], $billingAddress->getRegionId()); + self::assertEquals($data['country_id'], $billingAddress->getCountryId()); + + $shippingAddress = $updatedQuote->getShippingAddress(); + self::assertEquals($data['city'], $shippingAddress->getCity()); + self::assertEquals($data['street'], $shippingAddress->getStreet()); + } } diff --git a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php index 4a40e19c818e73d79f2ae46979373dfd78e2c255..319269bd0011de09180ebaff82c3d2ceb82e0f21 100644 --- a/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php +++ b/lib/internal/Magento/Framework/App/DeploymentConfig/Writer/PhpFormatter.php @@ -21,21 +21,48 @@ class PhpFormatter implements FormatterInterface public function format($data, array $comments = []) { if (!empty($comments) && is_array($data)) { - $elements = []; + return "<?php\nreturn array (\n" . $this->formatData($data, $comments, ' ') . "\n);\n"; + } + return "<?php\nreturn " . var_export($data, true) . ";\n"; + } + + /** + * Format supplied data + * + * @param $data + * @param $comments + * @param string $prefix + * @return string + */ + protected function formatData($data, $comments, $prefix = '') + { + $elements = []; + + if (is_array($data)) { foreach ($data as $key => $value) { - $comment = ' '; if (!empty($comments[$key])) { - $section = " * For the section: " . $key . "\n"; - $exportedComment = is_string($comments[$key]) - ? $comments[$key] - : var_export($comments[$key], true); - $comment = " /**\n" . $section . " * " . str_replace("\n", "\n * ", $exportedComment) . "\n */\n"; + $elements[] = $prefix . '/**'; + $elements[] = $prefix . ' * For the section: ' . $key; + + foreach (explode("\n", $comments[$key]) as $commentLine) { + $elements[] = $prefix . ' * ' . $commentLine; + } + + $elements[] = $prefix . " */"; + } + + $elements[] = $prefix . var_export($key, true) . ' => ' . + (!is_array($value) ? var_export($value, true) . ',' : ''); + + if (is_array($value)) { + $elements[] = $prefix . 'array ('; + $elements[] = $this->formatData($value, [], ' ' . $prefix); + $elements[] = $prefix . '),'; } - $space = is_array($value) ? " \n" : ' '; - $elements[] = $comment . var_export($key, true) . ' =>' . $space . var_export($value, true); } - return "<?php\nreturn array (\n" . implode(",\n", str_replace("\n", "\n ", $elements)) . "\n);\n"; + return implode("\n", $elements); } - return "<?php\nreturn " . var_export($data, true) . ";\n"; + + return var_export($data, true); } } diff --git a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php index d77edbf86aaad7c13190707925a5ed3a86a485e9..fe57a5a7ad8f3e0416390dcf4099aa3acae11aaf 100644 --- a/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php +++ b/lib/internal/Magento/Framework/App/Test/Unit/DeploymentConfig/Writer/PhpFormatterTest.php @@ -5,7 +5,7 @@ */ namespace Magento\Framework\App\Test\Unit\DeploymentConfig\Writer; -use \Magento\Framework\App\DeploymentConfig\Writer\PhpFormatter; +use Magento\Framework\App\DeploymentConfig\Writer\PhpFormatter; class PhpFormatterTest extends \PHPUnit\Framework\TestCase { @@ -81,7 +81,7 @@ return array ( ), ), 'ns3' => 'just text', - 'ns4' => 'just text' + 'ns4' => 'just text', ); TEXT; @@ -126,7 +126,7 @@ return array ( * For the section: ns4 * comment for namespace 4 */ - 'ns4' => 'just text' + 'ns4' => 'just text', ); TEXT; diff --git a/lib/internal/Magento/Framework/Shell/CommandRenderer.php b/lib/internal/Magento/Framework/Shell/CommandRenderer.php index 32e75a7420d1eaba91ec454fffd6bf87ee120a7a..6cf91d09479e1e7c4cb51498c097583740c4a340 100644 --- a/lib/internal/Magento/Framework/Shell/CommandRenderer.php +++ b/lib/internal/Magento/Framework/Shell/CommandRenderer.php @@ -16,8 +16,8 @@ class CommandRenderer implements CommandRendererInterface */ public function render($command, array $arguments = []) { + $command = preg_replace('/(\s+2>&1)*(\s*\|)|$/', ' 2>&1$2', $command); $arguments = array_map('escapeshellarg', $arguments); - $command = preg_replace('/\s?\||$/', ' 2>&1$0', $command); if (empty($arguments)) { return $command; } diff --git a/lib/internal/Magento/Framework/Shell/Test/Unit/CommandRendererTest.php b/lib/internal/Magento/Framework/Shell/Test/Unit/CommandRendererTest.php index dcc091473eeec9f040d15b0ccbf35417a3d6389b..213e2afa2c6551903c47559cf127bf004278e90b 100644 --- a/lib/internal/Magento/Framework/Shell/Test/Unit/CommandRendererTest.php +++ b/lib/internal/Magento/Framework/Shell/Test/Unit/CommandRendererTest.php @@ -5,27 +5,40 @@ */ namespace Magento\Framework\Shell\Test\Unit; -use \Magento\Framework\Shell\CommandRenderer; +use Magento\Framework\Shell\CommandRenderer; class CommandRendererTest extends \PHPUnit\Framework\TestCase { - public function testRender() + /** + * @param $expectedCommand + * @param $actualCommand + * @param $testArguments + * @dataProvider commandsDataProvider + */ + public function testRender($expectedCommand, $actualCommand, $testArguments) { - $testArgument = 'argument'; - $testArgument2 = 'argument2'; $commandRenderer = new CommandRenderer(); $this->assertEquals( - "php -r " . escapeshellarg($testArgument) . " 2>&1 | grep " . escapeshellarg($testArgument2) . " 2>&1", - $commandRenderer->render('php -r %s | grep %s', [$testArgument, $testArgument2]) + $expectedCommand, + $commandRenderer->render($actualCommand, $testArguments) ); } - public function testRenderWithoutArguments() + public function commandsDataProvider() { - $commandRenderer = new CommandRenderer(); - $this->assertEquals( - "php -r %s 2>&1 | grep %s 2>&1", - $commandRenderer->render('php -r %s | grep %s', []) - ); + $testArgument = 'argument'; + $testArgument2 = 'argument2'; + + $expectedCommand = "php -r %s 2>&1 | grep %s 2>&1"; + $expectedCommandArgs = "php -r '" . $testArgument . "' 2>&1 | grep '" . $testArgument2 . "' 2>&1"; + + return [ + [$expectedCommand, 'php -r %s | grep %s', []], + [$expectedCommand, 'php -r %s 2>&1 | grep %s', []], + [$expectedCommand, 'php -r %s 2>&1 2>&1 | grep %s', []], + [$expectedCommandArgs, 'php -r %s | grep %s', [$testArgument, $testArgument2]], + [$expectedCommandArgs, 'php -r %s 2>&1 | grep %s', [$testArgument, $testArgument2]], + [$expectedCommandArgs, 'php -r %s 2>&1 2>&1 | grep %s', [$testArgument, $testArgument2]], + ]; } } diff --git a/lib/internal/Magento/Framework/View/Page/Config.php b/lib/internal/Magento/Framework/View/Page/Config.php index 248841e47499dd88fc60ed5a7b601aa066d51511..6fadc91bd73eeca60226ec70108a1479293415f3 100644 --- a/lib/internal/Magento/Framework/View/Page/Config.php +++ b/lib/internal/Magento/Framework/View/Page/Config.php @@ -173,7 +173,7 @@ class Config $this->setElementAttribute( self::ELEMENT_TYPE_HTML, self::HTML_ATTRIBUTE_LANG, - str_replace('_', '-', $this->localeResolver->getLocale()) + strstr($this->localeResolver->getLocale(), '_', true) ); } diff --git a/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php b/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php index 940bcbbd6d7f1a2856637e279830c5040439e5d3..4ad8e7c229bfdedd5084182e4289f801faa7cc62 100644 --- a/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php +++ b/setup/src/Magento/Setup/Console/Command/AdminUserCreateCommand.php @@ -6,13 +6,14 @@ namespace Magento\Setup\Console\Command; -use Magento\Setup\Model\AdminAccount; use Magento\Framework\Setup\ConsoleLogger; +use Magento\Setup\Model\AdminAccount; use Magento\Setup\Model\InstallerFactory; use Magento\User\Model\UserValidationRules; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Question\Question; class AdminUserCreateCommand extends AbstractSetupCommand { @@ -50,6 +51,98 @@ class AdminUserCreateCommand extends AbstractSetupCommand parent::configure(); } + /** + * @param \Symfony\Component\Console\Input\InputInterface $input + * @param \Symfony\Component\Console\Output\OutputInterface $output + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + protected function interact(InputInterface $input, OutputInterface $output) + { + /** @var \Symfony\Component\Console\Helper\QuestionHelper $questionHelper */ + $questionHelper = $this->getHelper('question'); + + if (!$input->getOption(AdminAccount::KEY_USER)) { + $question = new Question('<question>Admin user:</question> ', ''); + $this->addNotEmptyValidator($question); + + $input->setOption( + AdminAccount::KEY_USER, + $questionHelper->ask($input, $output, $question) + ); + } + + if (!$input->getOption(AdminAccount::KEY_PASSWORD)) { + $question = new Question('<question>Admin password:</question> ', ''); + $question->setHidden(true); + + $question->setValidator(function ($value) use ($output) { + $user = new \Magento\Framework\DataObject(); + $user->setPassword($value); + + $validator = new \Magento\Framework\Validator\DataObject(); + $this->validationRules->addPasswordRules($validator); + + $validator->isValid($user); + foreach ($validator->getMessages() as $message) { + throw new \Exception($message); + } + + return $value; + }); + + $input->setOption( + AdminAccount::KEY_PASSWORD, + $questionHelper->ask($input, $output, $question) + ); + } + + if (!$input->getOption(AdminAccount::KEY_EMAIL)) { + $question = new Question('<question>Admin email:</question> ', ''); + $this->addNotEmptyValidator($question); + + $input->setOption( + AdminAccount::KEY_EMAIL, + $questionHelper->ask($input, $output, $question) + ); + } + + if (!$input->getOption(AdminAccount::KEY_FIRST_NAME)) { + $question = new Question('<question>Admin first name:</question> ', ''); + $this->addNotEmptyValidator($question); + + $input->setOption( + AdminAccount::KEY_FIRST_NAME, + $questionHelper->ask($input, $output, $question) + ); + } + + if (!$input->getOption(AdminAccount::KEY_LAST_NAME)) { + $question = new Question('<question>Admin last name:</question> ', ''); + $this->addNotEmptyValidator($question); + + $input->setOption( + AdminAccount::KEY_LAST_NAME, + $questionHelper->ask($input, $output, $question) + ); + } + } + + /** + * @param \Symfony\Component\Console\Question\Question $question + * @return void + */ + private function addNotEmptyValidator(Question $question) + { + $question->setValidator(function ($value) { + if (trim($value) == '') { + throw new \Exception('The value cannot be empty'); + } + + return $value; + }); + } + /** * {@inheritdoc} */ @@ -57,7 +150,7 @@ class AdminUserCreateCommand extends AbstractSetupCommand { $errors = $this->validate($input); if ($errors) { - $output->writeln('<error>' . implode('</error>' . PHP_EOL . '<error>', $errors) . '</error>'); + $output->writeln('<error>' . implode('</error>' . PHP_EOL . '<error>', $errors) . '</error>'); // we must have an exit code higher than zero to indicate something was wrong return \Magento\Framework\Console\Cli::RETURN_FAILURE; } @@ -113,7 +206,7 @@ class AdminUserCreateCommand extends AbstractSetupCommand ? '' : $input->getOption(AdminAccount::KEY_PASSWORD) ); - $validator = new \Magento\Framework\Validator\DataObject; + $validator = new \Magento\Framework\Validator\DataObject(); $this->validationRules->addUserInfoRules($validator); $this->validationRules->addPasswordRules($validator); 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 1cfd0c9494a519dcf4b22f32033f2fc2a30b3d75..d244f48d4e1ea3b90fad815e4aa8268886cbb690 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/AdminUserCreateCommandTest.php @@ -5,14 +5,21 @@ */ namespace Magento\Setup\Test\Unit\Console\Command; -use Magento\Setup\Model\AdminAccount; use Magento\Setup\Console\Command\AdminUserCreateCommand; +use Magento\Setup\Model\AdminAccount; use Magento\Setup\Mvc\Bootstrap\InitParamListener; use Magento\User\Model\UserValidationRules; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Tester\CommandTester; class AdminUserCreateCommandTest extends \PHPUnit\Framework\TestCase { + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Symfony\Component\Console\Helper\QuestionHelper + */ + private $questionHelperMock; + /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Setup\Model\InstallerFactory */ @@ -27,6 +34,10 @@ class AdminUserCreateCommandTest extends \PHPUnit\Framework\TestCase { $this->installerFactoryMock = $this->createMock(\Magento\Setup\Model\InstallerFactory::class); $this->command = new AdminUserCreateCommand($this->installerFactoryMock, new UserValidationRules()); + + $this->questionHelperMock = $this->getMockBuilder(QuestionHelper::class) + ->setMethods(['ask']) + ->getMock(); } public function testExecute() @@ -50,10 +61,70 @@ class AdminUserCreateCommandTest extends \PHPUnit\Framework\TestCase $installerMock = $this->createMock(\Magento\Setup\Model\Installer::class); $installerMock->expects($this->once())->method('installAdminUser')->with($data); $this->installerFactoryMock->expects($this->once())->method('create')->willReturn($installerMock); - $commandTester->execute($options); + $commandTester->execute($options, ['interactive' => false]); $this->assertEquals('Created Magento administrator user named user' . PHP_EOL, $commandTester->getDisplay()); } + public function testInteraction() + { + $application = new Application(); + $application->add($this->command); + + $this->questionHelperMock->expects($this->at(0)) + ->method('ask') + ->will($this->returnValue('admin')); + + $this->questionHelperMock->expects($this->at(1)) + ->method('ask') + ->will($this->returnValue('Password123')); + + $this->questionHelperMock->expects($this->at(2)) + ->method('ask') + ->will($this->returnValue('john.doe@example.com')); + + $this->questionHelperMock->expects($this->at(3)) + ->method('ask') + ->will($this->returnValue('John')); + + $this->questionHelperMock->expects($this->at(4)) + ->method('ask') + ->will($this->returnValue('Doe')); + + // We override the standard helper with our mock + $this->command->getHelperSet()->set($this->questionHelperMock, 'question'); + + $installerMock = $this->createMock(\Magento\Setup\Model\Installer::class); + + $expectedData = [ + 'admin-user' => 'admin', + 'admin-password' => 'Password123', + 'admin-email' => 'john.doe@example.com', + 'admin-firstname' => 'John', + 'admin-lastname' => 'Doe', + 'magento-init-params' => null, + 'help' => false, + 'quiet' => false, + 'verbose' => false, + 'version' => false, + 'ansi' => false, + 'no-ansi' => false, + 'no-interaction' => false, + ]; + + $installerMock->expects($this->once())->method('installAdminUser')->with($expectedData); + $this->installerFactoryMock->expects($this->once())->method('create')->willReturn($installerMock); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([ + 'command' => $this->command->getName(), + ]); + + $this->assertEquals( + 'Created Magento administrator user named admin' . PHP_EOL, + $commandTester->getDisplay() + ); + } + public function testGetOptionsList() { /* @var $argsList \Symfony\Component\Console\Input\InputArgument[] */