diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 12ad4e452b1c7..2b1720ccaabae 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,11 +5,12 @@ - Information on your environment, - Steps to reproduce, - Expected and actual results, + Fields marked with (*) are required. Please don't remove the template. Please also have a look at our guidelines article before adding a new issue https://github.com/magento/magento2/wiki/Issue-reporting-guidelines --> -### Preconditions +### Preconditions (*) 1. [Screenshots, logs or description] -### Actual result +### Actual result (*) 1. [Screenshots, logs or description] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 17aa66c919eb5..33a6ef02ace11 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,28 +6,29 @@ about: Technical issue with the Magento 2 core components -### Preconditions +### Preconditions (*) 1. 2. -### Steps to reproduce +### Steps to reproduce (*) 1. 2. -### Expected result +### Expected result (*) 1. [Screenshots, logs or description] 2. -### Actual result +### Actual result (*) 1. [Screenshots, logs or description] 2. diff --git a/.github/ISSUE_TEMPLATE/developer-experience-issue.md b/.github/ISSUE_TEMPLATE/developer-experience-issue.md index a66b0c62ef8e2..423d4818fb31c 100644 --- a/.github/ISSUE_TEMPLATE/developer-experience-issue.md +++ b/.github/ISSUE_TEMPLATE/developer-experience-issue.md @@ -6,12 +6,13 @@ about: Issues related to customization, extensibility, modularity -### Summary +### Summary (*) -### Examples +### Examples (*) ### Proposed solution diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index de85da43b70fa..f64185773cab4 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -5,13 +5,13 @@ about: Please consider reporting directly to https://github.com/magento/communit --- -### Description +### Description (*) -### Expected behavior +### Expected behavior (*) ### Benefits diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5b0b9d74e453b..f191bd9aaba67 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,12 +3,13 @@ To help us process this pull request we recommend that you add the following information: - Summary of the pull request, - Issue(s) related to the changes made, - - Manual testing scenarios, + - Manual testing scenarios + Fields marked with (*) are required. Please don't remove the template. --> -### Description +### Description (*) + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationGeneralSectionPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationGeneralSectionPage.xml new file mode 100644 index 0000000000000..c0c4f4bd9d3a5 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationGeneralSectionPage.xml @@ -0,0 +1,11 @@ + + + +
+ + diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminConfigurationGeneralSectionCountryOptionsGroupSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminConfigurationGeneralSectionCountryOptionsGroupSection.xml new file mode 100644 index 0000000000000..e6f5e599c605d --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminConfigurationGeneralSectionCountryOptionsGroupSection.xml @@ -0,0 +1,11 @@ + + +
+ +
+
diff --git a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml index c56a8768b0adf..bba375c2d6bfd 100644 --- a/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml +++ b/app/code/Magento/Backend/Test/Mftf/Section/AdminMainActionsSection.xml @@ -7,9 +7,9 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
- +
diff --git a/app/code/Magento/Backend/Test/Mftf/composer.json b/app/code/Magento/Backend/Test/Mftf/composer.json deleted file mode 100644 index c77f5a21f37fd..0000000000000 --- a/app/code/Magento/Backend/Test/Mftf/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "magento/functional-test-module-backend", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-developer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev", - "magento/functional-test-module-security": "100.0.0-dev", - "magento/functional-test-module-backup": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-translation": "100.0.0-dev", - "magento/functional-test-module-require-js": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Backend/Test/Unit/Block/DataProviders/UploadConfigTest.php b/app/code/Magento/Backend/Test/Unit/Block/DataProviders/UploadConfigTest.php new file mode 100644 index 0000000000000..481c08aed4a01 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Block/DataProviders/UploadConfigTest.php @@ -0,0 +1,65 @@ +config = $this->getMockBuilder(ScopeConfig::class) + ->setMethods(['getValue']) + ->getMockForAbstractClass(); + $this->objectManagerHelper = new ObjectManagerHelper($this); + $this->uploadConfig = $this->objectManagerHelper->getObject( + UploadConfig::class, + [ + 'config' => $this->config + ] + ); + } + + /** + * @dataProvider configValuesDataProvider() + * @param int $configValue + * @param int $expectedValue + * @return void + */ + public function testGetIsResizeEnabled(int $configValue, int $expectedValue) + { + $this->config->expects($this->once()) + ->method('getValue') + ->with('system/upload_configuration/enable_resize') + ->willReturn($configValue); + $this->assertEquals($expectedValue, $this->uploadConfig->getIsResizeEnabled()); + } + + public function configValuesDataProvider(): array + { + return [ + [1, 1], + [0, 0] + ]; + } +} diff --git a/app/code/Magento/Backend/Test/Unit/Service/V1/ModuleServiceTest.php b/app/code/Magento/Backend/Test/Unit/Service/V1/ModuleServiceTest.php new file mode 100644 index 0000000000000..c7ff1d95617b6 --- /dev/null +++ b/app/code/Magento/Backend/Test/Unit/Service/V1/ModuleServiceTest.php @@ -0,0 +1,71 @@ +moduleListMock = $this->createMock(ModuleListInterface::class); + $this->objectManager = new ObjectManager($this); + $this->moduleService = $this->objectManager->getObject( + ModuleService::class, + [ + 'moduleList' => $this->moduleListMock, + ] + ); + } + + /** + * Test getModules method + * + * @return void + */ + public function testGetModules() + { + $moduleNames = ['Magento_Backend', 'Magento_Catalog', 'Magento_Customer']; + $this->moduleListMock->expects($this->once())->method('getNames')->willReturn($moduleNames); + + $expected = $moduleNames; + $actual = $this->moduleService->getModules(); + $this->assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index 4494e51b9337c..845bc4ec87402 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -24,7 +24,7 @@ "magento/module-theme": "100.2.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Backend/etc/adminhtml/system.xml b/app/code/Magento/Backend/etc/adminhtml/system.xml index 0e76031c9b75e..e3411166ee4a4 100644 --- a/app/code/Magento/Backend/etc/adminhtml/system.xml +++ b/app/code/Magento/Backend/etc/adminhtml/system.xml @@ -218,6 +218,7 @@ Magento\Directory\Model\Config\Source\Country + 1 @@ -321,15 +322,26 @@ - + + + Magento\Config\Model\Config\Source\Yesno + Resize performed via javascript before file upload. + + validate-greater-than-zero validate-number required-entry Maximum allowed width for uploaded image. + + 1 + - + validate-greater-than-zero validate-number required-entry Maximum allowed height for uploaded image. + + 1 +
diff --git a/app/code/Magento/Backend/etc/config.xml b/app/code/Magento/Backend/etc/config.xml index 45d283ad3ff22..8283fa18dd370 100644 --- a/app/code/Magento/Backend/etc/config.xml +++ b/app/code/Magento/Backend/etc/config.xml @@ -29,6 +29,7 @@ 1 + 1 1920 1200 diff --git a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml index 805e9783f3f18..52d5dd6d114ee 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/admin/login.phtml @@ -43,7 +43,7 @@ data-validate="{required:true}" value="" placeholder="" - autocomplete="new-password" + autocomplete="off" /> diff --git a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml index 966372773f295..50cfdc4e07681 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/media/uploader.phtml @@ -14,7 +14,8 @@ "Magento_Backend/js/media-uploader" : { "maxFileSize": getFileSizeService()->getMaxFileSize() ?>, "maxWidth":getImageUploadMaxWidth() ?> , - "maxHeight": getImageUploadMaxHeight() ?> + "maxHeight": getImageUploadMaxHeight() ?>, + "isResizeEnabled": getImageUploadConfigData()->getIsResizeEnabled() ?> } }' > 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 3c65c0358eb57..978d338c3993b 100644 --- a/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml +++ b/app/code/Magento/Backend/view/adminhtml/templates/system/search.phtml @@ -39,7 +39,7 @@ <% if (data.items.length) { %> <% _.each(data.items, function(value){ %>
  • + <%= data.optionData(value) %> > <%- value.name %> <%- value.type %> diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js index 03403a4ec4a04..34e388bac3d33 100644 --- a/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js +++ b/app/code/Magento/Backend/view/adminhtml/web/js/media-uploader.js @@ -25,9 +25,20 @@ define([ * @private */ _create: function () { - var - self = this, - progressTmpl = mageTemplate('[data-template="uploader"]'); + var self = this, + progressTmpl = mageTemplate('[data-template="uploader"]'), + isResizeEnabled = this.options.isResizeEnabled, + resizeConfiguration = { + action: 'resize', + maxWidth: this.options.maxWidth, + maxHeight: this.options.maxHeight + }; + + if (!isResizeEnabled) { + resizeConfiguration = { + action: 'resize' + }; + } this.element.find('input[type=file]').fileupload({ dataType: 'json', @@ -44,8 +55,7 @@ define([ * @param {Object} data */ add: function (e, data) { - var - fileSize, + var fileSize, tmpl; $.each(data.files, function (index, file) { @@ -115,11 +125,9 @@ define([ process: [{ action: 'load', fileTypes: /^image\/(gif|jpeg|png)$/ - }, { - action: 'resize', - maxWidth: this.options.maxWidth, - maxHeight: this.options.maxHeight - }, { + }, + resizeConfiguration, + { action: 'save' }] }); diff --git a/app/code/Magento/Backup/Model/Config/Backend/Cron.php b/app/code/Magento/Backup/Model/Config/Backend/Cron.php index 4855ef1129502..c91ab07b063ef 100644 --- a/app/code/Magento/Backup/Model/Config/Backend/Cron.php +++ b/app/code/Magento/Backup/Model/Config/Backend/Cron.php @@ -76,13 +76,13 @@ public function afterSave() if ($enabled) { $cronExprArray = [ - intval($time[1]), # Minute - intval($time[0]), # Hour + (int)$time[1], # Minute + (int)$time[0], # Hour $frequency == $frequencyMonthly ? '1' : '*', # Day of the Month '*', # Month of the Year - $frequency == $frequencyWeekly ? '1' : '*', # Day of the Week + $frequency == $frequencyWeekly ? '1' : '*', # Day of the Week ]; - $cronExprString = join(' ', $cronExprArray); + $cronExprString = implode(' ', $cronExprArray); } else { $cronExprString = ''; } diff --git a/app/code/Magento/Backup/Test/Mftf/composer.json b/app/code/Magento/Backup/Test/Mftf/composer.json deleted file mode 100644 index 4468e4cdf7436..0000000000000 --- a/app/code/Magento/Backup/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-backup", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-cron": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Backup/composer.json b/app/code/Magento/Backup/composer.json index b45fc8d2a2bef..f0cbb27bd8ab2 100644 --- a/app/code/Magento/Backup/composer.json +++ b/app/code/Magento/Backup/composer.json @@ -9,7 +9,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php index a035c84b4cafd..4d63ee4125b74 100644 --- a/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php @@ -49,6 +49,8 @@ public function build(array $buildSubject) $payment = $paymentDO->getPayment(); $data = $payment->getAdditionalInformation(); + // the payment token could be stored only if a customer checks the Vault flow on storefront + // see https://developers.braintreepayments.com/guides/paypal/vault/javascript/v2#invoking-the-vault-flow if (!empty($data[VaultConfigProvider::IS_ACTIVE_CODE])) { $result[self::$optionsKey] = [ self::$storeInVaultOnSuccess => true diff --git a/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php index 4280663178efb..5b773e03c8870 100644 --- a/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php +++ b/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php @@ -6,6 +6,7 @@ namespace Magento\Braintree\Gateway\Request; use Magento\Braintree\Gateway\SubjectReader; +use Magento\Payment\Gateway\Command\CommandException; use Magento\Payment\Gateway\Request\BuilderInterface; use Magento\Payment\Helper\Formatter; @@ -41,6 +42,9 @@ public function build(array $buildSubject) $payment = $paymentDO->getPayment(); $extensionAttributes = $payment->getExtensionAttributes(); $paymentToken = $extensionAttributes->getVaultPaymentToken(); + if ($paymentToken === null) { + throw new CommandException(__('The Payment Token is not available to perform the request.')); + } return [ 'amount' => $this->formatPrice($this->subjectReader->readAmount($buildSubject)), 'paymentMethodToken' => $paymentToken->getGatewayToken() diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php index fe5895541543d..aa23fa767d1ed 100644 --- a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php +++ b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php @@ -148,7 +148,7 @@ private function updateBillingAddress(Quote $quote, array $details) { $billingAddress = $quote->getBillingAddress(); - if ($this->config->isRequiredBillingAddress()) { + if ($this->config->isRequiredBillingAddress() && !empty($details['billingAddress'])) { $this->updateAddressData($billingAddress, $details['billingAddress']); } else { $this->updateAddressData($billingAddress, $details['shippingAddress']); diff --git a/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php b/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php index e06b913db8ef4..4e422c795095f 100644 --- a/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/PayPal/ConfigProvider.php @@ -41,12 +41,14 @@ public function __construct(Config $config, ResolverInterface $resolver) } /** - * Retrieve assoc array of checkout configuration + * Retrieve assoc array of checkout configuration. * * @return array */ - public function getConfig() + public function getConfig(): array { + $requireBillingAddressAll = \Magento\Paypal\Model\Config::REQUIRE_BILLING_ADDRESS_ALL; + return [ 'payment' => [ self::PAYPAL_CODE => [ @@ -60,6 +62,8 @@ public function getConfig() 'vaultCode' => self::PAYPAL_VAULT_CODE, 'skipOrderReview' => $this->config->isSkipOrderReview(), 'paymentIcon' => $this->config->getPayPalIcon(), + 'isRequiredBillingAddress' => + (int)$this->config->isRequiredBillingAddress() === $requireBillingAddressAll, ] ] ]; diff --git a/app/code/Magento/Braintree/Test/Mftf/composer.json b/app/code/Magento/Braintree/Test/Mftf/composer.json deleted file mode 100644 index 4b110f1bbdf3e..0000000000000 --- a/app/code/Magento/Braintree/Test/Mftf/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "magento/functional-test-module-braintree", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-vault": "100.0.0-dev", - "magento/functional-test-module-instant-purchase": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-paypal": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-checkout-agreements": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php index 5af050002eb2d..80d333db80f0a 100644 --- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php @@ -3,16 +3,20 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Braintree\Test\Unit\Gateway\Request; -use Magento\Braintree\Gateway\SubjectReader; use Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder; +use Magento\Braintree\Gateway\SubjectReader; use Magento\Payment\Gateway\Data\PaymentDataObjectInterface; use Magento\Sales\Api\Data\OrderPaymentExtension; use Magento\Sales\Model\Order\Payment; use Magento\Vault\Model\PaymentToken; use PHPUnit_Framework_MockObject_MockObject as MockObject; +/** + * Tests VaultCaptureDataBuilder. + */ class VaultCaptureDataBuilderTest extends \PHPUnit\Framework\TestCase { /** @@ -30,7 +34,15 @@ class VaultCaptureDataBuilderTest extends \PHPUnit\Framework\TestCase */ private $payment; - public function setUp() + /** + * @var SubjectReader|MockObject + */ + private $subjectReader; + + /** + * @inheritdoc + */ + protected function setUp() { $this->paymentDO = $this->createMock(PaymentDataObjectInterface::class); $this->payment = $this->getMockBuilder(Payment::class) @@ -39,11 +51,15 @@ public function setUp() $this->paymentDO->method('getPayment') ->willReturn($this->payment); - $this->builder = new VaultCaptureDataBuilder(new SubjectReader()); + $this->subjectReader = $this->getMockBuilder(SubjectReader::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->builder = new VaultCaptureDataBuilder($this->subjectReader); } /** - * \Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder::build + * Checks the result after builder execution. */ public function testBuild() { @@ -51,19 +67,28 @@ public function testBuild() $token = '5tfm4c'; $buildSubject = [ 'payment' => $this->paymentDO, - 'amount' => $amount + 'amount' => $amount, ]; $expected = [ 'amount' => $amount, - 'paymentMethodToken' => $token + 'paymentMethodToken' => $token, ]; + $this->subjectReader->method('readPayment') + ->with($buildSubject) + ->willReturn($this->paymentDO); + $this->subjectReader->method('readAmount') + ->with($buildSubject) + ->willReturn($amount); + + /** @var OrderPaymentExtension|MockObject $paymentExtension */ $paymentExtension = $this->getMockBuilder(OrderPaymentExtension::class) ->setMethods(['getVaultPaymentToken']) ->disableOriginalConstructor() ->getMockForAbstractClass(); + /** @var PaymentToken|MockObject $paymentToken */ $paymentToken = $this->getMockBuilder(PaymentToken::class) ->disableOriginalConstructor() ->getMock(); @@ -79,4 +104,39 @@ public function testBuild() $result = $this->builder->build($buildSubject); self::assertEquals($expected, $result); } + + /** + * Checks a builder execution if Payment Token doesn't exist. + * + * @expectedException \Magento\Payment\Gateway\Command\CommandException + * @expectedExceptionMessage The Payment Token is not available to perform the request. + */ + public function testBuildWithoutPaymentToken(): void + { + $amount = 30.00; + $buildSubject = [ + 'payment' => $this->paymentDO, + 'amount' => $amount, + ]; + + $this->subjectReader->method('readPayment') + ->with($buildSubject) + ->willReturn($this->paymentDO); + $this->subjectReader->method('readAmount') + ->with($buildSubject) + ->willReturn($amount); + + /** @var OrderPaymentExtension|MockObject $paymentExtension */ + $paymentExtension = $this->getMockBuilder(OrderPaymentExtension::class) + ->setMethods(['getVaultPaymentToken']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->payment->method('getExtensionAttributes') + ->willReturn($paymentExtension); + $paymentExtension->method('getVaultPaymentToken') + ->willReturn(null); + + $this->builder->build($buildSubject); + } } diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php index 39863e6561c43..7475d81a56142 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php @@ -3,22 +3,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Braintree\Test\Unit\Model\Paypal\Helper; +use Magento\Braintree\Gateway\Config\PayPal\Config; +use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater; +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider; +use Magento\Braintree\Observer\DataAssignObserver; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartExtensionInterface; use Magento\Quote\Model\Quote; use Magento\Quote\Model\Quote\Address; use Magento\Quote\Model\Quote\Payment; -use Magento\Quote\Api\CartRepositoryInterface; -use Magento\Braintree\Model\Ui\PayPal\ConfigProvider; -use Magento\Braintree\Observer\DataAssignObserver; -use Magento\Braintree\Gateway\Config\PayPal\Config; -use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Class QuoteUpdaterTest * - * @see \Magento\Braintree\Model\Paypal\Helper\QuoteUpdater - * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase @@ -26,39 +28,42 @@ class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase const TEST_NONCE = '3ede7045-2aea-463e-9754-cd658ffeeb48'; /** - * @var Config|\PHPUnit_Framework_MockObject_MockObject + * @var Config|MockObject */ - private $configMock; + private $config; /** - * @var CartRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var CartRepositoryInterface|MockObject */ - private $quoteRepositoryMock; + private $quoteRepository; /** - * @var Address|\PHPUnit_Framework_MockObject_MockObject + * @var Address|MockObject */ - private $billingAddressMock; + private $billingAddress; /** - * @var Address|\PHPUnit_Framework_MockObject_MockObject + * @var Address|MockObject */ - private $shippingAddressMock; + private $shippingAddress; /** * @var QuoteUpdater */ private $quoteUpdater; + /** + * @inheritdoc + */ protected function setUp() { - $this->configMock = $this->getMockBuilder(Config::class) + $this->config = $this->getMockBuilder(Config::class) ->disableOriginalConstructor() ->getMock(); - $this->quoteRepositoryMock = $this->getMockBuilder(CartRepositoryInterface::class) + $this->quoteRepository = $this->getMockBuilder(CartRepositoryInterface::class) ->getMockForAbstractClass(); - $this->billingAddressMock = $this->getMockBuilder(Address::class) + $this->billingAddress = $this->getMockBuilder(Address::class) ->setMethods( [ 'setLastname', @@ -73,9 +78,10 @@ protected function setUp() 'setShouldIgnoreValidation', 'getEmail' ] - )->disableOriginalConstructor() + ) + ->disableOriginalConstructor() ->getMock(); - $this->shippingAddressMock = $this->getMockBuilder(Address::class) + $this->shippingAddress = $this->getMockBuilder(Address::class) ->setMethods( [ 'setLastname', @@ -89,54 +95,61 @@ protected function setUp() 'setPostcode', 'setShouldIgnoreValidation' ] - )->disableOriginalConstructor() + ) + ->disableOriginalConstructor() ->getMock(); $this->quoteUpdater = new QuoteUpdater( - $this->configMock, - $this->quoteRepositoryMock + $this->config, + $this->quoteRepository ); } + /** + * Checks if quote details can be update by the response from Braintree. + * + * @throws \Magento\Framework\Exception\LocalizedException + */ public function testExecute() { $details = $this->getDetails(); - $quoteMock = $this->getQuoteMock(); - $paymentMock = $this->getPaymentMock(); + $quote = $this->getQuoteMock(); + $payment = $this->getPaymentMock(); - $quoteMock->expects(self::once()) - ->method('getPayment') - ->willReturn($paymentMock); + $quote->method('getPayment') + ->willReturn($payment); - $paymentMock->expects(self::once()) - ->method('setMethod') + $payment->method('setMethod') ->with(ConfigProvider::PAYPAL_CODE); - $paymentMock->expects(self::once()) - ->method('setAdditionalInformation') + $payment->method('setAdditionalInformation') ->with(DataAssignObserver::PAYMENT_METHOD_NONCE, self::TEST_NONCE); - $this->updateQuoteStep($quoteMock, $details); + $this->updateQuoteStep($quote, $details); - $this->quoteUpdater->execute(self::TEST_NONCE, $details, $quoteMock); + $this->quoteUpdater->execute(self::TEST_NONCE, $details, $quote); } + /** + * Disables quote's addresses validation. + * + * @return void + */ private function disabledQuoteAddressValidationStep() { - $this->billingAddressMock->expects(self::once()) - ->method('setShouldIgnoreValidation') + $this->billingAddress->method('setShouldIgnoreValidation') ->with(true); - $this->shippingAddressMock->expects(self::once()) - ->method('setShouldIgnoreValidation') + $this->shippingAddress->method('setShouldIgnoreValidation') ->with(true); - $this->billingAddressMock->expects(self::once()) - ->method('getEmail') + $this->billingAddress->method('getEmail') ->willReturn('bt_buyer_us@paypal.com'); } /** + * Gets quote's details. + * * @return array */ - private function getDetails() + private function getDetails(): array { return [ 'email' => 'bt_buyer_us@paypal.com', @@ -166,54 +179,51 @@ private function getDetails() } /** + * Updates shipping address details. + * * @param array $details */ private function updateShippingAddressStep(array $details) { - $this->shippingAddressMock->expects(self::once()) - ->method('setLastname') + $this->shippingAddress->method('setLastname') ->with($details['lastName']); - $this->shippingAddressMock->expects(self::once()) - ->method('setFirstname') + $this->shippingAddress->method('setFirstname') ->with($details['firstName']); - $this->shippingAddressMock->expects(self::once()) - ->method('setEmail') + $this->shippingAddress->method('setEmail') ->with($details['email']); - $this->shippingAddressMock->expects(self::once()) - ->method('setCollectShippingRates') + $this->shippingAddress->method('setCollectShippingRates') ->with(true); - $this->updateAddressDataStep($this->shippingAddressMock, $details['shippingAddress']); + $this->updateAddressDataStep($this->shippingAddress, $details['shippingAddress']); } /** - * @param \PHPUnit_Framework_MockObject_MockObject $addressMock + * Updates address details. + * + * @param MockObject $address * @param array $addressData */ - private function updateAddressDataStep(\PHPUnit_Framework_MockObject_MockObject $addressMock, array $addressData) + private function updateAddressDataStep(MockObject $address, array $addressData) { - $addressMock->expects(self::once()) - ->method('setStreet') + $address->method('setStreet') ->with([$addressData['streetAddress'], $addressData['extendedAddress']]); - $addressMock->expects(self::once()) - ->method('setCity') + $address->method('setCity') ->with($addressData['locality']); - $addressMock->expects(self::once()) - ->method('setRegionCode') + $address->method('setRegionCode') ->with($addressData['region']); - $addressMock->expects(self::once()) - ->method('setCountryId') + $address->method('setCountryId') ->with($addressData['countryCodeAlpha2']); - $addressMock->expects(self::once()) - ->method('setPostcode') + $address->method('setPostcode') ->with($addressData['postalCode']); } /** - * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock + * Updates quote's address details. + * + * @param MockObject $quoteMock * @param array $details */ - private function updateQuoteAddressStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock, array $details) + private function updateQuoteAddressStep(MockObject $quoteMock, array $details) { $quoteMock->expects(self::exactly(2)) ->method('getIsVirtual') @@ -224,82 +234,92 @@ private function updateQuoteAddressStep(\PHPUnit_Framework_MockObject_MockObject } /** + * Updates billing address details. + * * @param array $details */ private function updateBillingAddressStep(array $details) { - $this->configMock->expects(self::once()) - ->method('isRequiredBillingAddress') + $this->config->method('isRequiredBillingAddress') ->willReturn(true); - $this->updateAddressDataStep($this->billingAddressMock, $details['billingAddress']); + $this->updateAddressDataStep($this->billingAddress, $details['billingAddress']); - $this->billingAddressMock->expects(self::once()) - ->method('setLastname') + $this->billingAddress->method('setLastname') ->with($details['lastName']); - $this->billingAddressMock->expects(self::once()) - ->method('setFirstname') + $this->billingAddress->method('setFirstname') ->with($details['firstName']); - $this->billingAddressMock->expects(self::once()) - ->method('setEmail') + $this->billingAddress->method('setEmail') ->with($details['email']); } /** - * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock + * Updates quote details. + * + * @param MockObject $quote * @param array $details */ - private function updateQuoteStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock, array $details) + private function updateQuoteStep(MockObject $quote, array $details) { - $quoteMock->expects(self::once()) - ->method('setMayEditShippingAddress') + $quote->method('setMayEditShippingAddress') ->with(false); - $quoteMock->expects(self::once()) - ->method('setMayEditShippingMethod') + $quote->method('setMayEditShippingMethod') ->with(true); - $quoteMock->expects(self::exactly(2)) - ->method('getShippingAddress') - ->willReturn($this->shippingAddressMock); - $quoteMock->expects(self::exactly(2)) + $quote->method('getShippingAddress') + ->willReturn($this->shippingAddress); + $quote->expects(self::exactly(2)) ->method('getBillingAddress') - ->willReturn($this->billingAddressMock); + ->willReturn($this->billingAddress); - $this->updateQuoteAddressStep($quoteMock, $details); + $this->updateQuoteAddressStep($quote, $details); $this->disabledQuoteAddressValidationStep(); - $quoteMock->expects(self::once()) - ->method('collectTotals'); + $quote->method('collectTotals'); - $this->quoteRepositoryMock->expects(self::once()) - ->method('save') - ->with($quoteMock); + $this->quoteRepository->method('save') + ->with($quote); } /** - * @return Quote|\PHPUnit_Framework_MockObject_MockObject + * Creates a mock for Quote object. + * + * @return Quote|MockObject */ - private function getQuoteMock() + private function getQuoteMock(): MockObject { - return $this->getMockBuilder(Quote::class) + $quote = $this->getMockBuilder(Quote::class) ->setMethods( [ 'getIsVirtual', 'getPayment', + 'getExtensionAttributes', 'setMayEditShippingAddress', 'setMayEditShippingMethod', 'collectTotals', 'getShippingAddress', 'getBillingAddress', ] - )->disableOriginalConstructor() + ) + ->disableOriginalConstructor() ->getMock(); + + $cartExtension = $this->getMockBuilder(CartExtensionInterface::class) + ->setMethods(['setShippingAssignments']) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $quote->method('getExtensionAttributes') + ->willReturn($cartExtension); + return $quote; } /** - * @return Payment|\PHPUnit_Framework_MockObject_MockObject + * Creates a mock for Payment object. + * + * @return Payment|MockObject */ - private function getPaymentMock() + private function getPaymentMock(): MockObject { return $this->getMockBuilder(Payment::class) ->disableOriginalConstructor() diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php index 22f7f46bd98f1..d3dba400507f6 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/PayPal/ConfigProviderTest.php @@ -47,9 +47,10 @@ protected function setUp() } /** - * Run test getConfig method + * Run test getConfig method. * * @param array $expected + * @return void * @dataProvider getConfigDataProvider */ public function testGetConfig($expected) @@ -77,13 +78,16 @@ public function testGetConfig($expected) 'width' => 30, 'height' => 26, 'url' => 'https://icon.test.url' ]); + $this->config->method('isRequiredBillingAddress') + ->willReturn(1); + self::assertEquals($expected, $this->configProvider->getConfig()); } /** * @return array */ - public function getConfigDataProvider() + public function getConfigDataProvider(): array { return [ [ @@ -101,7 +105,8 @@ public function getConfigDataProvider() 'skipOrderReview' => false, 'paymentIcon' => [ 'width' => 30, 'height' => 26, 'url' => 'https://icon.test.url' - ] + ], + 'isRequiredBillingAddress' => true, ] ] ] diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json index 8af2198949f1c..bf85cdd94a474 100644 --- a/app/code/Magento/Braintree/composer.json +++ b/app/code/Magento/Braintree/composer.json @@ -25,7 +25,7 @@ "magento/module-theme": "100.2.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "proprietary" ], diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv index 6bf677151ed0d..7bd305f546dc6 100644 --- a/app/code/Magento/Braintree/i18n/en_US.csv +++ b/app/code/Magento/Braintree/i18n/en_US.csv @@ -192,3 +192,4 @@ Currency,Currency "Too many concurrent attempts to refund this transaction. Try again later.","Too many concurrent attempts to refund this transaction. Try again later." "Too many concurrent attempts to void this transaction. Try again later.","Too many concurrent attempts to void this transaction. Try again later." "Braintree Settlement","Braintree Settlement" +"The Payment Token is not available to perform the request.","The Payment Token is not available to perform the request." diff --git a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js index 253f3530701bc..c60504f2c44ce 100644 --- a/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js +++ b/app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/paypal.js @@ -206,7 +206,9 @@ define([ beforePlaceOrder: function (data) { this.setPaymentMethodNonce(data.nonce); - if (quote.billingAddress() === null && typeof data.details.billingAddress !== 'undefined') { + if ((this.isRequiredBillingAddress() || quote.billingAddress() === null) && + typeof data.details.billingAddress !== 'undefined' + ) { this.setBillingAddress(data.details, data.details.billingAddress); } @@ -264,6 +266,14 @@ define([ return window.checkoutConfig.payment[this.getCode()].isAllowShippingAddressOverride; }, + /** + * Is billing address required from PayPal side. + * @returns {Boolean} + */ + isRequiredBillingAddress: function () { + return window.checkoutConfig.payment[this.getCode()].isRequiredBillingAddress; + }, + /** * Get configuration for PayPal * @returns {Object} diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php index fd2b8efa8eebd..fc8706ce54d06 100644 --- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php +++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php @@ -7,6 +7,7 @@ use Magento\Bundle\Model\Option; use Magento\Catalog\Model\Product; +use Magento\Framework\DataObject; /** * Catalog bundle product info block @@ -166,7 +167,7 @@ public function getJsonConfig() $defaultValues = []; $preConfiguredFlag = $currentProduct->hasPreconfiguredValues(); - /** @var \Magento\Framework\DataObject|null $preConfiguredValues */ + /** @var DataObject|null $preConfiguredValues */ $preConfiguredValues = $preConfiguredFlag ? $currentProduct->getPreconfiguredValues() : null; $position = 0; @@ -185,12 +186,13 @@ public function getJsonConfig() if ($configValue) { $defaultValues[$optionId] = $configValue; } + $options = $this->processOptions($optionId, $options, $preConfiguredValues); } $position++; } $config = $this->getConfigData($currentProduct, $options); - $configObj = new \Magento\Framework\DataObject( + $configObj = new DataObject( [ 'config' => $config, ] @@ -393,4 +395,30 @@ private function getConfigData(Product $product, array $options) ]; return $config; } + + /** + * Set preconfigured quantities and selections to options. + * + * @param string $optionId + * @param array $options + * @param DataObject $preConfiguredValues + * @return array + */ + private function processOptions(string $optionId, array $options, DataObject $preConfiguredValues) + { + $preConfiguredQtys = $preConfiguredValues->getData("bundle_option_qty/${optionId}") ?? []; + $selections = $options[$optionId]['selections']; + array_walk($selections, function (&$selection, $selectionId) use ($preConfiguredQtys) { + if (is_array($preConfiguredQtys) && isset($preConfiguredQtys[$selectionId])) { + $selection['qty'] = $preConfiguredQtys[$selectionId]; + } else { + if ((int)$preConfiguredQtys > 0) { + $selection['qty'] = $preConfiguredQtys; + } + } + }); + $options[$optionId]['selections'] = $selections; + + return $options; + } } diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php index 7b136a822084d..22849bcb20276 100644 --- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php +++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php @@ -169,7 +169,9 @@ protected function _getSelectedOptions() */ protected function assignSelection(\Magento\Bundle\Model\Option $option, $selectionId) { - if ($selectionId && $option->getSelectionById($selectionId)) { + if (is_array($selectionId)) { + $this->_selectedOptions = $selectionId; + } else if ($selectionId && $option->getSelectionById($selectionId)) { $this->_selectedOptions = $selectionId; } elseif (!$option->getRequired()) { $this->_selectedOptions = 'None'; diff --git a/app/code/Magento/Bundle/Model/Product/Type.php b/app/code/Magento/Bundle/Model/Product/Type.php index bec77bce1d879..afcb09f2f43fe 100644 --- a/app/code/Magento/Bundle/Model/Product/Type.php +++ b/app/code/Magento/Bundle/Model/Product/Type.php @@ -8,13 +8,13 @@ namespace Magento\Bundle\Model\Product; -use Magento\Framework\App\ObjectManager; +use Magento\Bundle\Model\ResourceModel\Selection\Collection as Selections; +use Magento\Bundle\Model\ResourceModel\Selection\Collection\FilterApplier as SelectionCollectionFilterApplier; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Pricing\PriceCurrencyInterface; use Magento\Framework\Serialize\Serializer\Json; -use Magento\Framework\EntityManager\MetadataPool; -use Magento\Bundle\Model\ResourceModel\Selection\Collection\FilterApplier as SelectionCollectionFilterApplier; -use Magento\Bundle\Model\ResourceModel\Selection\Collection as Selections; /** * Bundle Type Model @@ -539,7 +539,7 @@ public function updateQtyOption($options, \Magento\Framework\DataObject $option, foreach ($options as $quoteItemOption) { if ($quoteItemOption->getCode() == 'selection_qty_' . $selection->getSelectionId()) { if ($optionUpdateFlag) { - $quoteItemOption->setValue(intval($quoteItemOption->getValue())); + $quoteItemOption->setValue((int)$quoteItemOption->getValue()); } else { $quoteItemOption->setValue($value); } @@ -561,7 +561,7 @@ public function updateQtyOption($options, \Magento\Framework\DataObject $option, */ public function prepareQuoteItemQty($qty, $product) { - return intval($qty); + return (int)$qty; } /** @@ -1013,10 +1013,8 @@ public function shakeSelections($firstItem, $secondItem) $secondItem->getPosition(), $secondItem->getSelectionId(), ]; - if ($aPosition == $bPosition) { - return 0; - } - return $aPosition < $bPosition ? -1 : 1; + + return $aPosition <=> $bPosition; } /** diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php index ca6d7f1030121..5b88288ff72ca 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php @@ -151,13 +151,7 @@ protected function _construct() */ public function _afterLoad() { - parent::_afterLoad(); - if ($this->getStoreId() && $this->_items) { - foreach ($this->_items as $item) { - $item->setStoreId($this->getStoreId()); - } - } - return $this; + return parent::_afterLoad(); } /** @@ -356,7 +350,10 @@ public function addPriceFilter($product, $searchMin, $useRegularPrice = false) } /** + * Get Catalog Rule Processor. + * * @return \Magento\CatalogRule\Model\ResourceModel\Product\CollectionProcessor + * * @deprecated 100.2.0 */ private function getCatalogRuleProcessor() diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml new file mode 100644 index 0000000000000..72140cf6d5848 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/AdminCreateApiBundleProductActionGroup.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + 4.99 + + + 2.89 + + + 7.33 + + + 18.25 + + + + {{productName}} + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4.99 + + + 2.89 + + + 7.33 + + + 18.25 + + + + {{productName}} + + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml new file mode 100644 index 0000000000000..fda5d10295676 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/StorefrontProductCartActionGroup.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml index 28fca883350e1..7123a573bc2e1 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/BundleLinkData.xml @@ -9,7 +9,6 @@ - 1 diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductData.xml index 0fded251b6f1e..2977f423d7e67 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/BundleProductData.xml @@ -20,7 +20,6 @@ bundleproduct 4 TestOption - Drop-down 10 20 Drop-down diff --git a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml index c55b0166b04ba..ecdc4da3a75b6 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Data/ProductData.xml @@ -7,7 +7,7 @@ --> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> Api Bundle Product api-bundle-product @@ -37,4 +37,19 @@ CustomAttributeDynamicPrice CustomAttributePriceViewRange + + Api Fixed Bundle Product + api-fixed-bundle-product + bundle + 4 + 1.23 + 4 + 1 + api-fixed-bundle-product + EavStockItem + ApiProductDescription + ApiProductShortDescription + CustomAttributeFixPrice + CustomAttributePriceView + diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml new file mode 100644 index 0000000000000..46ac1cdae93c0 --- /dev/null +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontAddBundleProductWithZeroPriceToShoppingCartTest.xml @@ -0,0 +1,78 @@ + + + + + + + + + + <description value="Add Bundle product with zero price to shopping cart"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-83535"/> + <group value="bundle"/> + </annotations> + <before> + <!--Enable freeShipping--> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShipping"/> + <!--Create category--> + <createData entity="SimpleSubCategory" stepKey="createSubCategory"/> + <!--Create simple with zero price product--> + <createData entity="ApiProductWithDescription" stepKey="apiSimple"> + <field key="price">0</field> + </createData> + <!--Create Bundle product--> + <createData entity="ApiBundleProductPriceViewRange" stepKey="apiBundleProduct"> + <requiredEntity createDataKey="createSubCategory"/> + </createData> + <!--Create Attribute--> + <createData entity="DropDownBundleOption" stepKey="bundleOption"> + <requiredEntity createDataKey="apiBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="createBundleLink"> + <requiredEntity createDataKey="apiBundleProduct"/> + <requiredEntity createDataKey="bundleOption"/> + <requiredEntity createDataKey="apiSimple"/> + </createData> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + </before> + <after> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShipping"/> + <deleteData createDataKey="apiSimple" stepKey="deleteSimple"/> + <deleteData createDataKey="apiBundleProduct" stepKey="deleteBundleProduct"/> + <deleteData createDataKey="createSubCategory" stepKey="deleteCategory"/> + <actionGroup ref="AdminOrdersGridClearFiltersActionGroup" stepKey="clearFilters"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Open category page--> + <amOnPage url="{{StorefrontCategoryPage.url($$createSubCategory.custom_attributes[url_key]$$)}}" stepKey="amOnCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <!--Add bundle product to cart--> + <actionGroup ref="StorefrontAddCategoryBundleProductToCartActionGroup" stepKey="addBundleProductToCart"> + <argument name="product" value="$$apiBundleProduct$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + + <!--Place order--> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"> + <argument name="shippingMethod" value="Free Shipping"/> + </actionGroup> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="checkoutPlaceOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber}}" stepKey="grabOrderNumber"/> + + <!--Check subtotal in created order--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="filterOrderGridById" stepKey="filterOrderById"> + <argument name="orderId" value="$grabOrderNumber"/> + </actionGroup> + <click selector="{{AdminOrdersGridSection.firstRow}}" stepKey="clickOrderRow"/> + <scrollTo selector="{{AdminOrderTotalSection.subTotal}}" stepKey="scrollToOrderTotalSection"/> + <see selector="{{AdminOrderTotalSection.subTotal}}" userInput="$0.00" stepKey="checkSubtotal"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml index f7d41d3d2907c..e65a4dc99902b 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml @@ -90,14 +90,13 @@ <!-- Go to the shopping cart page and edit the first product --> <amOnPage url="{{CheckoutCartPage.url}}" stepKey="onPageShoppingCart"/> <waitForPageLoad stepKey="waitForCartPageLoad"/> - <waitForElementVisible selector="{{CheckoutCartSummarySection.total}}" stepKey="waitForInfoDropdown"/> + <waitForElementVisible selector="{{StorefrontCheckoutCartSummarySection.total}}" stepKey="waitForInfoDropdown"/> <waitForPageLoad stepKey="waitForCartPageLoad3"/> - <grabTextFrom selector="{{CheckoutCartSummarySection.total}}" stepKey="grabTotalBefore"/> + <grabTextFrom selector="{{StorefrontCheckoutCartSummarySection.total}}" stepKey="grabTotalBefore"/> <click selector="{{CheckoutCartProductSection.editItemParametersButton('1')}}" stepKey="clickEdit"/> <waitForPageLoad stepKey="waitForStorefront2"/> - <!-- Choose both of the options on the storefront --> - <click selector="{{StorefrontBundledSection.bundleOption('1','1')}}" stepKey="selectFirstBundleOption2"/> + <!-- Check second one option to choose both of the options on the storefront --> <click selector="{{StorefrontBundledSection.bundleOption('1','2')}}" stepKey="selectSecondBundleOption2"/> <waitForPageLoad stepKey="waitForPriceUpdate3"/> @@ -113,9 +112,9 @@ <!-- Assert that the options are both there and the proce no longer matches --> <see selector="{{CheckoutCartProductSection.itemOptionsBlock('2')}}" userInput="$$simpleProduct1.sku$$" stepKey="assertBothOptions"/> <see selector="{{CheckoutCartProductSection.itemOptionsBlock('2')}}" userInput="$$simpleProduct2.sku$$" stepKey="assertBothOptions2"/> - <waitForElementVisible selector="{{CheckoutCartSummarySection.total}}" stepKey="waitForInfoDropdown2"/> + <waitForElementVisible selector="{{StorefrontCheckoutCartSummarySection.total}}" stepKey="waitForInfoDropdown2"/> <waitForPageLoad stepKey="waitForCartPageLoad4"/> - <grabTextFrom selector="{{CheckoutCartSummarySection.total}}" stepKey="grabTotalAfter"/> + <grabTextFrom selector="{{StorefrontCheckoutCartSummarySection.total}}" stepKey="grabTotalAfter"/> <assertNotEquals expected="{$grabTotalBefore}" expectedType="string" actual="{$grabTotalAfter}" actualType="string" stepKey="assertNotEquals"/> <!-- Delete the bundled product --> diff --git a/app/code/Magento/Bundle/Test/Mftf/composer.json b/app/code/Magento/Bundle/Test/Mftf/composer.json deleted file mode 100644 index 45fc41118245c..0000000000000 --- a/app/code/Magento/Bundle/Test/Mftf/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "magento/functional-test-module-bundle", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-catalog-rule": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-gift-message": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-webapi": "100.0.0-dev", - "magento/functional-test-module-bundle-sample-data": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json index c9e5f87a1afad..fe883e783d6ff 100644 --- a/app/code/Magento/Bundle/composer.json +++ b/app/code/Magento/Bundle/composer.json @@ -26,7 +26,7 @@ "magento/module-sales-rule": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/BundleImportExport/Test/Mftf/composer.json b/app/code/Magento/BundleImportExport/Test/Mftf/composer.json deleted file mode 100644 index 43f373fc2a628..0000000000000 --- a/app/code/Magento/BundleImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-bundle-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json index 59698c598419b..b21da5c7ae5b9 100644 --- a/app/code/Magento/BundleImportExport/composer.json +++ b/app/code/Magento/BundleImportExport/composer.json @@ -12,7 +12,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CacheInvalidate/Test/Mftf/composer.json b/app/code/Magento/CacheInvalidate/Test/Mftf/composer.json deleted file mode 100644 index e299cd437fd28..0000000000000 --- a/app/code/Magento/CacheInvalidate/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-cache-invalidate", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json index 1331114ca50c4..825c9937c16d1 100644 --- a/app/code/Magento/CacheInvalidate/composer.json +++ b/app/code/Magento/CacheInvalidate/composer.json @@ -7,7 +7,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Captcha/Test/Mftf/composer.json b/app/code/Magento/Captcha/Test/Mftf/composer.json deleted file mode 100644 index e50dfeffb6646..0000000000000 --- a/app/code/Magento/Captcha/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-captcha", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Captcha/composer.json b/app/code/Magento/Captcha/composer.json index 7842d7bc68bf1..a8a63e19e67df 100644 --- a/app/code/Magento/Captcha/composer.json +++ b/app/code/Magento/Captcha/composer.json @@ -13,7 +13,7 @@ "zendframework/zend-session": "^2.7.3" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php index ee92fd7c19b80..6f6ad4f909815 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php @@ -81,12 +81,10 @@ protected function _prepareForm() */ protected function _getSetId() { - return intval( - $this->getRequest()->getParam('id') - ) > 0 ? intval( - $this->getRequest()->getParam('id') - ) : $this->_typeFactory->create()->load( - $this->_coreRegistry->registry('entityType') - )->getDefaultAttributeSetId(); + return (int)$this->getRequest()->getParam('id') > 0 + ? (int)$this->getRequest()->getParam('id') + : $this->_typeFactory->create()->load( + $this->_coreRegistry->registry('entityType') + )->getDefaultAttributeSetId(); } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php index 9c7604d27eae0..2b7074e4c26d2 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Attributes.php @@ -16,8 +16,11 @@ use Magento\Framework\Data\Form\Element\AbstractElement; /** + * Attributes tab block + * * @api * @SuppressWarnings(PHPMD.DepthOfInheritance) + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @since 100.0.2 */ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements @@ -33,6 +36,9 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements */ protected $_attributeAction; + /** @var array */ + private $excludeFields; + /** * @param \Magento\Backend\Block\Template\Context $context * @param \Magento\Framework\Registry $registry @@ -40,6 +46,7 @@ class Attributes extends \Magento\Catalog\Block\Adminhtml\Form implements * @param \Magento\Catalog\Model\ProductFactory $productFactory * @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeAction * @param array $data + * @param array $excludeFields */ public function __construct( \Magento\Backend\Block\Template\Context $context, @@ -47,14 +54,19 @@ public function __construct( \Magento\Framework\Data\FormFactory $formFactory, \Magento\Catalog\Model\ProductFactory $productFactory, \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeAction, - array $data = [] + array $data = [], + array $excludeFields = [] ) { $this->_attributeAction = $attributeAction; $this->_productFactory = $productFactory; + $this->excludeFields = $excludeFields; + parent::__construct($context, $registry, $formFactory, $data); } /** + * Construct block + * * @return void */ protected function _construct() @@ -64,20 +76,14 @@ protected function _construct() } /** + * Prepares form + * * @return void + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _prepareForm() { - $this->setFormExcludedFieldList( - [ - 'category_ids', - 'gallery', - 'image', - 'media_gallery', - 'quantity_and_stock_status', - 'tier_price', - ] - ); + $this->setFormExcludedFieldList($this->getExcludedFields()); $this->_eventManager->dispatch( 'adminhtml_catalog_product_form_prepare_excluded_field_list', ['object' => $this] @@ -155,6 +161,8 @@ protected function _getAdditionalElementHtml($element) } /** + * Returns tab label + * * @return \Magento\Framework\Phrase */ public function getTabLabel() @@ -163,6 +171,8 @@ public function getTabLabel() } /** + * Return Tab title + * * @return \Magento\Framework\Phrase */ public function getTabTitle() @@ -171,6 +181,8 @@ public function getTabTitle() } /** + * Can show tab in tabs + * * @return bool */ public function canShowTab() @@ -179,10 +191,22 @@ public function canShowTab() } /** + * Tab not hidden + * * @return bool */ public function isHidden() { return false; } + + /** + * Returns excluded fields + * + * @return array + */ + private function getExcludedFields(): array + { + return $this->excludeFields; + } } diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php index 750bf6f8a0216..4aebd521fe60d 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Action/Attribute/Tab/Inventory.php @@ -74,7 +74,7 @@ public function getFieldSuffix() public function getStoreId() { $storeId = $this->getRequest()->getParam('store'); - return intval($storeId); + return (int)$storeId; } /** diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php index ac9e75493bdc3..dd7554a9b7b19 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/Content.php @@ -17,9 +17,16 @@ use Magento\Framework\View\Element\AbstractBlock; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\App\ObjectManager; +use Magento\Backend\Block\DataProviders\UploadConfig as ImageUploadConfigDataProvider; class Content extends \Magento\Backend\Block\Widget { + /** + * @var ImageUploadConfigDataProvider + */ + private $imageUploadConfigDataProvider; + /** * @var string */ @@ -45,16 +52,20 @@ class Content extends \Magento\Backend\Block\Widget * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder * @param \Magento\Catalog\Model\Product\Media\Config $mediaConfig * @param array $data + * @param ImageUploadConfigDataProvider $imageUploadConfigDataProvider */ public function __construct( \Magento\Backend\Block\Template\Context $context, \Magento\Framework\Json\EncoderInterface $jsonEncoder, \Magento\Catalog\Model\Product\Media\Config $mediaConfig, - array $data = [] + array $data = [], + ImageUploadConfigDataProvider $imageUploadConfigDataProvider = null ) { $this->_jsonEncoder = $jsonEncoder; $this->_mediaConfig = $mediaConfig; parent::__construct($context, $data); + $this->imageUploadConfigDataProvider = $imageUploadConfigDataProvider + ?: ObjectManager::getInstance()->get(ImageUploadConfigDataProvider::class); } /** @@ -62,7 +73,11 @@ public function __construct( */ protected function _prepareLayout() { - $this->addChild('uploader', \Magento\Backend\Block\Media\Uploader::class); + $this->addChild( + 'uploader', + \Magento\Backend\Block\Media\Uploader::class, + ['image_upload_config_data' => $this->imageUploadConfigDataProvider] + ); $this->getUploader()->getConfig()->setUrl( $this->_urlBuilder->addSessionParam()->getUrl('catalog/product_gallery/upload') diff --git a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php index 40afd44305262..726f083c07cd4 100644 --- a/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php +++ b/app/code/Magento/Catalog/Block/Product/ProductList/Upsell.php @@ -171,8 +171,8 @@ public function getRowCount() */ public function setColumnCount($columns) { - if (intval($columns) > 0) { - $this->_columnCount = intval($columns); + if ((int)$columns > 0) { + $this->_columnCount = (int)$columns; } return $this; } @@ -214,8 +214,8 @@ public function getIterableItem() */ public function setItemLimit($type, $limit) { - if (intval($limit) > 0) { - $this->_itemLimits[$type] = intval($limit); + if ((int)$limit > 0) { + $this->_itemLimits[$type] = (int)$limit; } return $this; } diff --git a/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php b/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php index 704271b58f483..e880e0aba35cc 100644 --- a/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php +++ b/app/code/Magento/Catalog/Block/Product/Widget/NewWidget.php @@ -139,7 +139,7 @@ public function getCacheKeyInfo() [ $this->getDisplayType(), $this->getProductsPerPage(), - intval($this->getRequest()->getParam($this->getData('page_var_name'), 1)), + (int)$this->getRequest()->getParam($this->getData('page_var_name'), 1), $this->serializer->serialize($this->getRequest()->getParams()) ] ); diff --git a/app/code/Magento/Catalog/Console/Command/PriceIndexerDimensionsModeSetCommand.php b/app/code/Magento/Catalog/Console/Command/PriceIndexerDimensionsModeSetCommand.php deleted file mode 100644 index 0660d4d6ceb36..0000000000000 --- a/app/code/Magento/Catalog/Console/Command/PriceIndexerDimensionsModeSetCommand.php +++ /dev/null @@ -1,196 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Catalog\Console\Command; - -use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; -use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Console\Input\InputOption; -use Symfony\Component\Console\Input\InputArgument; -use Magento\Framework\Exception\LocalizedException; -use Magento\Indexer\Console\Command\AbstractIndexerCommand; -use Magento\Framework\App\ObjectManagerFactory; -use Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcher; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\App\Config\ConfigResource\ConfigInterface; -use Magento\Framework\App\Cache\TypeListInterface; - -/** - * Command to change price indexer dimensions mode - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class PriceIndexerDimensionsModeSetCommand extends AbstractIndexerCommand -{ - const INPUT_KEY_MODE = 'mode'; - - /** - * ScopeConfigInterface - * - * @var ScopeConfigInterface - */ - private $configReader; - - /** - * ConfigInterface - * - * @var ConfigInterface - */ - private $configWriter; - - /** - * TypeListInterface - * - * @var TypeListInterface - */ - private $cacheTypeList; - - /** - * ModeSwitcher - * - * @var ModeSwitcher - */ - private $modeSwitcher; - - /** - * @param ObjectManagerFactory $objectManagerFactory - * @param ScopeConfigInterface $configReader - * @param ConfigInterface $configWriter - * @param TypeListInterface $cacheTypeList - * @param ModeSwitcher $modeSwitcher - */ - public function __construct( - ObjectManagerFactory $objectManagerFactory, - ScopeConfigInterface $configReader, - ConfigInterface $configWriter, - TypeListInterface $cacheTypeList, - ModeSwitcher $modeSwitcher - ) { - $this->configReader = $configReader; - $this->configWriter = $configWriter; - $this->cacheTypeList = $cacheTypeList; - $this->modeSwitcher = $modeSwitcher; - parent::__construct($objectManagerFactory); - } - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this->setName('indexer:set-dimensions-mode:catalog_product_price') - ->setDescription('Set Indexer Dimensions Mode') - ->setDefinition($this->getInputList()); - - parent::configure(); - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $errors = $this->validate($input); - - if ($errors) { - throw new \InvalidArgumentException(implode(PHP_EOL, $errors)); - } - - $returnValue = \Magento\Framework\Console\Cli::RETURN_SUCCESS; - - $indexer = $this->getObjectManager()->get(\Magento\Indexer\Model\Indexer::class); - $indexer->load(\Magento\Catalog\Model\Indexer\Product\Price\Processor::INDEXER_ID); - - try { - $currentMode = $input->getArgument(self::INPUT_KEY_MODE); - $previousMode = $this->configReader->getValue(ModeSwitcher::XML_PATH_PRICE_DIMENSIONS_MODE) ?: - DimensionModeConfiguration::DIMENSION_NONE; - - if ($previousMode !== $currentMode) { - //Create new tables and move data - $this->modeSwitcher->createTables($currentMode); - $this->modeSwitcher->moveData($currentMode, $previousMode); - - //Change config options - $this->configWriter->saveConfig(ModeSwitcher::XML_PATH_PRICE_DIMENSIONS_MODE, $currentMode); - $this->cacheTypeList->cleanType('config'); - $indexer->invalidate(); - - //Delete old tables - $this->modeSwitcher->dropTables($previousMode); - - $output->writeln( - 'Dimensions mode for indexer ' . $indexer->getTitle() . ' was changed from \'' - . $previousMode . '\' to \'' . $currentMode . '\'' - ); - } else { - $output->writeln('Dimensions mode for indexer ' . $indexer->getTitle() . ' has not been changed'); - } - } catch (LocalizedException $e) { - $output->writeln($e->getMessage() . PHP_EOL); - // we must have an exit code higher than zero to indicate something was wrong - $returnValue = \Magento\Framework\Console\Cli::RETURN_FAILURE; - } catch (\Exception $e) { - $output->writeln($indexer->getTitle() . " indexer process unknown error:" . PHP_EOL); - $output->writeln($e->getMessage() . PHP_EOL); - // we must have an exit code higher than zero to indicate something was wrong - $returnValue = \Magento\Framework\Console\Cli::RETURN_FAILURE; - } - - return $returnValue; - } - - /** - * Get list of arguments for the command - * - * @return InputOption[] - */ - public function getInputList(): array - { - $modeOptions[] = new InputArgument( - self::INPUT_KEY_MODE, - InputArgument::REQUIRED, - 'Indexer dimensions mode ['. DimensionModeConfiguration::DIMENSION_NONE - . '|' . DimensionModeConfiguration::DIMENSION_WEBSITE - . '|' . DimensionModeConfiguration::DIMENSION_CUSTOMER_GROUP - . '|' . DimensionModeConfiguration::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP .']' - ); - return $modeOptions; - } - - /** - * Check if all admin options are provided - * - * @param InputInterface $input - * @return string[] - */ - public function validate(InputInterface $input): array - { - $errors = []; - - $acceptedModeValues = ' Accepted values for ' . self::INPUT_KEY_MODE . ' are \'' - . DimensionModeConfiguration::DIMENSION_NONE . '\', \'' - . DimensionModeConfiguration::DIMENSION_WEBSITE . '\', \'' - . DimensionModeConfiguration::DIMENSION_CUSTOMER_GROUP . '\', \'' - . DimensionModeConfiguration::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP . '\''; - - $inputMode = $input->getArgument(self::INPUT_KEY_MODE); - if (!$inputMode) { - $errors[] = 'Missing argument \'' . self::INPUT_KEY_MODE .'\'.' . $acceptedModeValues; - } elseif (!in_array( - $inputMode, - [ - DimensionModeConfiguration::DIMENSION_NONE, - DimensionModeConfiguration::DIMENSION_WEBSITE, - DimensionModeConfiguration::DIMENSION_CUSTOMER_GROUP, - DimensionModeConfiguration::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP - ] - )) { - $errors[] = $acceptedModeValues; - } - return $errors; - } -} diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php index 4cc0f2d89d179..e24b142411b83 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php @@ -54,14 +54,6 @@ public function execute() try { $result = $this->imageUploader->saveFileToTmpDir($imageId); - - $result['cookie'] = [ - 'name' => $this->_getSession()->getName(), - 'value' => $this->_getSession()->getSessionId(), - 'lifetime' => $this->_getSession()->getCookieLifetime(), - 'path' => $this->_getSession()->getCookiePath(), - 'domain' => $this->_getSession()->getCookieDomain(), - ]; } catch (\Exception $e) { $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php index b9f9b739f4fa3..91a98424c9ae1 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php @@ -222,14 +222,14 @@ public function execute() $data['backend_model'] = $this->productHelper->getAttributeBackendModelByInputType( $data['frontend_input'] ); + + if ($model->getIsUserDefined() === null) { + $data['backend_type'] = $model->getBackendTypeByInput($data['frontend_input']); + } } $data += ['is_filterable' => 0, 'is_filterable_in_search' => 0]; - if ($model->getIsUserDefined() === null || $model->getIsUserDefined() != 0) { - $data['backend_type'] = $model->getBackendTypeByInput($data['frontend_input']); - } - $defaultValueField = $model->getDefaultValueByInput($data['frontend_input']); if ($defaultValueField) { $data['default_value'] = $this->getRequest()->getParam($defaultValueField); @@ -327,7 +327,8 @@ private function preprocessOptionsData(&$data) $serializedOptions = json_decode($data['serialized_options'], JSON_OBJECT_AS_ARRAY); foreach ($serializedOptions as $serializedOption) { $option = []; - parse_str($serializedOption, $option); + $serializedOptionWithParsedAmpersand = str_replace('&', '%26', $serializedOption); + parse_str($serializedOptionWithParsedAmpersand, $option); $data = array_replace_recursive($data, $option); } } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php index f32c6edd57394..d04284b4b323c 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php @@ -11,6 +11,7 @@ use Magento\Ui\Component\MassAction\Filter; use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NotFoundException; class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product { @@ -54,9 +55,15 @@ public function __construct( /** * @return \Magento\Backend\Model\View\Result\Redirect + * @throws NotFoundException + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\StateException */ public function execute() { + if (!$this->getRequest()->isPost()) { + throw new NotFoundException(__('Page not found')); + } $collection = $this->filter->getCollection($this->collectionFactory->create()); $productDeleted = 0; /** @var \Magento\Catalog\Model\Product $product */ diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php index 09c3a3864bbc7..b11696ed0d129 100644 --- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php +++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php @@ -102,7 +102,7 @@ private function getCategoryIds(Filter $filter): array } } - return array_unique(array_merge($categoryIds, ...$childCategoryIds)); + return array_map('intval', array_unique(array_merge($categoryIds, ...$childCategoryIds))); } /** diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index 57cfa75380851..00b093b2918f1 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -71,6 +71,11 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements const CACHE_TAG = 'cat_c'; + /** + * Category Store Id + */ + const STORE_ID = 'store_id'; + /**#@+ * Constants */ @@ -588,12 +593,12 @@ public function getStoreIds() * * If store id is underfined for category return current active store id * - * @return integer + * @return int */ public function getStoreId() { - if ($this->hasData('store_id')) { - return (int)$this->_getData('store_id'); + if ($this->hasData(self::STORE_ID)) { + return (int)$this->_getData(self::STORE_ID); } return (int)$this->_storeManager->getStore()->getId(); } @@ -609,7 +614,7 @@ public function setStoreId($storeId) if (!is_numeric($storeId)) { $storeId = $this->_storeManager->getStore($storeId)->getId(); } - $this->setData('store_id', $storeId); + $this->setData(self::STORE_ID, $storeId); $this->getResource()->setStoreId($storeId); return $this; } @@ -721,7 +726,7 @@ public function getParentId() return $parentId; } $parentIds = $this->getParentIds(); - return intval(array_pop($parentIds)); + return (int)array_pop($parentIds); } /** diff --git a/app/code/Magento/Catalog/Model/ImageExtractor.php b/app/code/Magento/Catalog/Model/ImageExtractor.php index 1c20608670672..f13d682f505cd 100644 --- a/app/code/Magento/Catalog/Model/ImageExtractor.php +++ b/app/code/Magento/Catalog/Model/ImageExtractor.php @@ -36,7 +36,7 @@ public function process(\DOMElement $mediaNode, $mediaParentTag) if ($attributeTagName === 'background') { $nodeValue = $this->processImageBackground($attribute->nodeValue); } elseif ($attributeTagName === 'width' || $attributeTagName === 'height') { - $nodeValue = intval($attribute->nodeValue); + $nodeValue = (int)$attribute->nodeValue; } else { $nodeValue = $attribute->nodeValue; } diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php index 7599e46f76e30..7f047327460ca 100644 --- a/app/code/Magento/Catalog/Model/ImageUploader.php +++ b/app/code/Magento/Catalog/Model/ImageUploader.php @@ -69,14 +69,9 @@ class ImageUploader /** * List of allowed image mime types * - * @var array + * @var string[] */ - private $allowedMimeTypes = [ - 'image/jpg', - 'image/jpeg', - 'image/gif', - 'image/png' - ]; + private $allowedMimeTypes; /** * ImageUploader constructor @@ -89,6 +84,7 @@ class ImageUploader * @param string $baseTmpPath * @param string $basePath * @param string[] $allowedExtensions + * @param string[] $allowedMimeTypes */ public function __construct( \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase, @@ -98,7 +94,8 @@ public function __construct( \Psr\Log\LoggerInterface $logger, $baseTmpPath, $basePath, - $allowedExtensions + $allowedExtensions, + $allowedMimeTypes = [] ) { $this->coreFileStorageDatabase = $coreFileStorageDatabase; $this->mediaDirectory = $filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA); @@ -108,6 +105,7 @@ public function __construct( $this->baseTmpPath = $baseTmpPath; $this->basePath = $basePath; $this->allowedExtensions = $allowedExtensions; + $this->allowedMimeTypes = $allowedMimeTypes; } /** @@ -167,7 +165,7 @@ public function getBasePath() } /** - * Retrieve base path + * Retrieve allowed extensions * * @return string[] */ diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php index 8f0e6e9c0cdb5..a94ff06c62ffb 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php @@ -113,7 +113,7 @@ public function getColumns() public function getMainStoreTable($storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID) { if (is_string($storeId)) { - $storeId = intval($storeId); + $storeId = (int)$storeId; } $suffix = sprintf('store_%d', $storeId); 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..f95807f615390 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; 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/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php index fbe0d4b550fa6..abe1b3eedbc84 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php @@ -341,6 +341,12 @@ protected function _updateTemporaryTableByStoreValues( if (!empty($changedIds)) { $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds)); } + + /* + * According to \Magento\Framework\DB\SelectRendererInterface select rendering may be updated + * so we need to trigger select renderer for correct update + */ + $select->assemble(); $sql = $select->crossUpdateFromSelect(['et' => $temporaryFlatTableName]); $this->_connection->query($sql); } @@ -355,6 +361,7 @@ protected function _updateTemporaryTableByStoreValues( if (!empty($changedIds)) { $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds)); } + $select->assemble(); $sql = $select->crossUpdateFromSelect(['et' => $temporaryFlatTableName]); $this->_connection->query($sql); } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php index e5935dac92a36..7a4d8e313462d 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php @@ -40,6 +40,7 @@ class DimensionModeConfiguration CustomerGroupDimensionProvider::DIMENSION_NAME ], ]; + /** * @var ScopeConfigInterface */ @@ -58,12 +59,23 @@ public function __construct(ScopeConfigInterface $scopeConfig) $this->scopeConfig = $scopeConfig; } + /** + * Return dimension modes configuration. + * + * @return array + */ + public function getDimensionModes(): array + { + return $this->modesMapping; + } + /** * Get names of dimensions which used for provided mode. * By default return dimensions for current enabled mode * * @param string|null $mode * @return string[] + * @throws \InvalidArgumentException */ public function getDimensionConfiguration(string $mode = null): array { @@ -81,7 +93,7 @@ public function getDimensionConfiguration(string $mode = null): array private function getCurrentMode(): string { if (null === $this->currentMode) { - $this->currentMode = $this->scopeConfig->getValue(ModeSwitcher::XML_PATH_PRICE_DIMENSIONS_MODE) + $this->currentMode = $this->scopeConfig->getValue(ModeSwitcherConfiguration::XML_PATH_PRICE_DIMENSIONS_MODE) ?: self::DIMENSION_NONE; } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php index b5f8425a855dc..e71031489fa0e 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php @@ -3,21 +3,21 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); + namespace Magento\Catalog\Model\Indexer\Product\Price; use Magento\Framework\Search\Request\Dimension; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Indexer\Model\DimensionModes; +use Magento\Indexer\Model\DimensionMode; /** * Class to prepare new tables for new indexer mode */ -class ModeSwitcher +class ModeSwitcher implements \Magento\Indexer\Model\ModeSwitcherInterface { - const XML_PATH_PRICE_DIMENSIONS_MODE = 'indexer/catalog_product_price/dimensions_mode'; - /** * TableMaintainer * @@ -38,15 +38,60 @@ class ModeSwitcher private $dimensionsArray; /** - * @param \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $tableMaintainer - * @param \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory + * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration + */ + private $dimensionModeConfiguration; + + /** + * @var ModeSwitcherConfiguration + */ + private $modeSwitcherConfiguration; + + /** + * @param TableMaintainer $tableMaintainer + * @param DimensionCollectionFactory $dimensionCollectionFactory + * @param DimensionModeConfiguration $dimensionModeConfiguration + * @param ModeSwitcherConfiguration $modeSwitcherConfiguration */ public function __construct( - \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $tableMaintainer, - \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory + TableMaintainer $tableMaintainer, + DimensionCollectionFactory $dimensionCollectionFactory, + DimensionModeConfiguration $dimensionModeConfiguration, + ModeSwitcherConfiguration $modeSwitcherConfiguration ) { $this->tableMaintainer = $tableMaintainer; $this->dimensionCollectionFactory = $dimensionCollectionFactory; + $this->dimensionModeConfiguration = $dimensionModeConfiguration; + $this->modeSwitcherConfiguration = $modeSwitcherConfiguration; + } + + /** + * @inheritdoc + */ + public function getDimensionModes(): DimensionModes + { + $dimensionsList = []; + foreach ($this->dimensionModeConfiguration->getDimensionModes() as $dimension => $modes) { + $dimensionsList[] = new DimensionMode($dimension, $modes); + } + + return new DimensionModes($dimensionsList); + } + + /** + * @inheritdoc + */ + public function switchMode(string $currentMode, string $previousMode) + { + //Create new tables and move data + $this->createTables($currentMode); + $this->moveData($currentMode, $previousMode); + + //Change config options + $this->modeSwitcherConfiguration->saveMode($currentMode); + + //Delete old tables + $this->dropTables($previousMode); } /** @@ -120,7 +165,7 @@ public function dropTables(string $previousMode) * * @param string $mode * - * @return array + * @return \Magento\Framework\Indexer\MultiDimensionProvider */ private function getDimensionsArray(string $mode): \Magento\Framework\Indexer\MultiDimensionProvider { diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php new file mode 100644 index 0000000000000..66b7147a8db76 --- /dev/null +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Catalog\Model\Indexer\Product\Price; + +use Magento\Framework\App\Config\ConfigResource\ConfigInterface; +use Magento\Framework\App\Cache\TypeListInterface; +use Magento\Indexer\Model\Indexer; + +/** + * Class to configure indexers and system config after modes has been switched + */ +class ModeSwitcherConfiguration +{ + const XML_PATH_PRICE_DIMENSIONS_MODE = 'indexer/catalog_product_price/dimensions_mode'; + + /** + * ConfigInterface + * + * @var ConfigInterface + */ + private $configWriter; + + /** + * TypeListInterface + * + * @var TypeListInterface + */ + private $cacheTypeList; + + /** + * @var Indexer $indexer + */ + private $indexer; + + /** + * @param ConfigInterface $configWriter + * @param TypeListInterface $cacheTypeList + * @param Indexer $indexer + */ + public function __construct( + ConfigInterface $configWriter, + TypeListInterface $cacheTypeList, + Indexer $indexer + ) { + $this->configWriter = $configWriter; + $this->cacheTypeList = $cacheTypeList; + $this->indexer = $indexer; + } + + /** + * Save switcher mode and invalidate reindex. + * + * @param string $mode + * @return void + * @throws \InvalidArgumentException + */ + public function saveMode(string $mode) + { + //Change config options + $this->configWriter->saveConfig(self::XML_PATH_PRICE_DIMENSIONS_MODE, $mode); + $this->cacheTypeList->cleanType('config'); + $this->indexer->load(\Magento\Catalog\Model\Indexer\Product\Price\Processor::INDEXER_ID); + $this->indexer->invalidate(); + } +} diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 850fb21777585..8a9233f176c61 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -525,9 +525,9 @@ protected function getCustomAttributesCodes() public function getStoreId() { if ($this->hasData(self::STORE_ID)) { - return $this->getData(self::STORE_ID); + return (int)$this->getData(self::STORE_ID); } - return $this->_storeManager->getStore()->getId(); + return (int)$this->_storeManager->getStore()->getId(); } /** @@ -809,6 +809,9 @@ public function getStoreIds() if (!$this->hasStoreIds()) { $storeIds = []; if ($websiteIds = $this->getWebsiteIds()) { + if ($this->_storeManager->isSingleStoreMode()) { + $websiteIds = array_keys($websiteIds); + } foreach ($websiteIds as $websiteId) { $websiteStores = $this->_storeManager->getWebsite($websiteId)->getStoreIds(); $storeIds = array_merge($storeIds, $websiteStores); @@ -982,7 +985,7 @@ public function setQty($qty) */ public function getQty() { - return $this->getData('qty'); + return (float)$this->getData('qty'); } /** diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php index c2108b0273bdb..dbefb09f06bd1 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Boolean.php @@ -25,7 +25,9 @@ public function beforeSave($object) $attributeCode = $this->getAttribute()->getName(); if ($object->getData('use_config_' . $attributeCode)) { $object->setData($attributeCode, BooleanSource::VALUE_USE_CONFIG); + return $this; } - return $this; + + return parent::beforeSave($object); } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php index a9da2a3400de9..752007aced094 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php @@ -97,6 +97,7 @@ protected function _generateUniqueSku($object) public function beforeSave($object) { $this->_generateUniqueSku($object); + $this->trimValue($object); return parent::beforeSave($object); } @@ -127,4 +128,17 @@ protected function _getLastSimilarAttributeValueIncrement($attribute, $object) $data = $connection->fetchOne($select, $bind); return abs((int)str_replace($value, '', $data)); } + + /** + * @param Product $object + * @return void + */ + private function trimValue($object) + { + $attrCode = $this->getAttribute()->getAttributeCode(); + $value = $object->getData($attrCode); + if ($value) { + $object->setData($attrCode, trim($value)); + } + } } diff --git a/app/code/Magento/Catalog/Model/Product/Compare/Item.php b/app/code/Magento/Catalog/Model/Product/Compare/Item.php index 8f18780ef8f71..777ed0fbe393b 100644 --- a/app/code/Magento/Catalog/Model/Product/Compare/Item.php +++ b/app/code/Magento/Catalog/Model/Product/Compare/Item.php @@ -158,8 +158,8 @@ public function addProductData($product) { if ($product instanceof Product) { $this->setProductId($product->getId()); - } elseif (intval($product)) { - $this->setProductId(intval($product)); + } elseif ((int)$product) { + $this->setProductId((int)$product); } return $this; diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php index f26e55099b054..9a4f3892fe115 100644 --- a/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php +++ b/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php @@ -16,7 +16,8 @@ class UpdateHandler extends \Magento\Catalog\Model\Product\Gallery\CreateHandler { /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ protected function processDeletedImages($product, array &$images) @@ -31,7 +32,7 @@ protected function processDeletedImages($product, array &$images) foreach ($images as &$image) { if (!empty($image['removed'])) { - if (!empty($image['value_id']) && !isset($picturesInOtherStores[$image['file']])) { + if (!empty($image['value_id'])) { if (preg_match('/\.\.(\\\|\/)/', $image['file'])) { continue; } @@ -52,7 +53,8 @@ protected function processDeletedImages($product, array &$images) } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ protected function processNewImage($product, array &$image) @@ -79,6 +81,8 @@ protected function processNewImage($product, array &$image) } /** + * Retrieve store ids from product. + * * @param \Magento\Catalog\Model\Product $product * @return array * @since 101.0.0 @@ -97,6 +101,8 @@ protected function extractStoreIds($product) } /** + * Remove deleted images. + * * @param array $files * @return null * @since 101.0.0 diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index ffae39cb6ed05..2463734503d88 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -36,7 +36,7 @@ class Image extends \Magento\Framework\Model\AbstractModel * * @var int */ - protected $_quality = 80; + protected $_quality = null; /** * @var bool @@ -294,7 +294,8 @@ public function setQuality($quality) */ public function getQuality() { - return $this->_quality; + return $this->_quality === null + ? $this->_scopeConfig->getValue('system/upload_configuration/jpeg_quality') : $this->_quality; } /** @@ -471,7 +472,7 @@ public function getImageProcessor() $this->_processor->keepTransparency($this->_keepTransparency); $this->_processor->constrainOnly($this->_constrainOnly); $this->_processor->backgroundColor($this->_backgroundColor); - $this->_processor->quality($this->_quality); + $this->_processor->quality($this->getQuality()); return $this->_processor; } @@ -494,7 +495,7 @@ public function resize() */ public function rotate($angle) { - $angle = intval($angle); + $angle = (int)$angle; $this->getImageProcessor()->rotate($angle); return $this; } @@ -854,7 +855,7 @@ private function getMiscParams() 'constrain_only' => ($this->_constrainOnly ? 'do' : 'not') . 'constrainonly', 'background' => $this->_rgbToString($this->_backgroundColor), 'angle' => $this->_angle, - 'quality' => $this->_quality, + 'quality' => $this->getQuality(), ]; // if has watermark add watermark params to hash diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php index cb6e76aebaadb..f6884a8d17f1f 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Date.php @@ -101,11 +101,11 @@ public function validateUserValue($values) $this->setUserValue( [ 'date' => isset($value['date']) ? $value['date'] : '', - 'year' => isset($value['year']) ? intval($value['year']) : 0, - 'month' => isset($value['month']) ? intval($value['month']) : 0, - 'day' => isset($value['day']) ? intval($value['day']) : 0, - 'hour' => isset($value['hour']) ? intval($value['hour']) : 0, - 'minute' => isset($value['minute']) ? intval($value['minute']) : 0, + 'year' => isset($value['year']) ? (int)$value['year'] : 0, + 'month' => isset($value['month']) ? (int)$value['month'] : 0, + 'day' => isset($value['day']) ? (int)$value['day'] : 0, + 'hour' => isset($value['hour']) ? (int)$value['hour'] : 0, + 'minute' => isset($value['minute']) ? (int)$value['minute'] : 0, 'day_part' => isset($value['day_part']) ? $value['day_part'] : '', 'date_internal' => isset($value['date_internal']) ? $value['date_internal'] : '', ] diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php new file mode 100644 index 0000000000000..8d4aea135eabb --- /dev/null +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Model\Product\Option\Type\File; + +/** + * Validator for existing (already saved) files. + */ +class ExistingValidate extends \Zend_Validate +{ + /** + * @inheritDoc + * + * @param string $value File's full path. + * @param string|null $originalName Original file's name (when uploaded). + */ + public function isValid($value, string $originalName = null) + { + $this->_messages = []; + $this->_errors = []; + + if (!is_string($value)) { + $this->_messages[] = __('Full file path is expected.')->render(); + return false; + } + + $result = true; + $fileInfo = null; + if ($originalName) { + $fileInfo = ['name' => $originalName]; + } + foreach ($this->_validators as $element) { + $validator = $element['instance']; + if ($validator->isValid($value, $fileInfo)) { + continue; + } + $result = false; + $messages = $validator->getMessages(); + $this->_messages = array_merge($this->_messages, $messages); + $this->_errors = array_merge($this->_errors, array_keys($messages)); + if ($element['breakChainOnFailure']) { + break; + } + } + return $result; + } +} diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php index 32c901afe8e74..c0d10c720f6f6 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php @@ -13,6 +13,6 @@ class ValidateFactory */ public function create() { - return new \Zend_Validate(); + return new ExistingValidate(); } } diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php index b54c66d75a058..49e062c5fd465 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php @@ -10,6 +10,8 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Catalog\Model\Product\Exception as ProductException; use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Math\Random; +use Magento\Framework\App\ObjectManager; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -62,12 +64,18 @@ class ValidatorFile extends Validator */ protected $isImageValidator; + /** + * @var Random + */ + private $random; + /** * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig * @param \Magento\Framework\Filesystem $filesystem * @param \Magento\Framework\File\Size $fileSize * @param \Magento\Framework\HTTP\Adapter\FileTransferFactory $httpFactory * @param \Magento\Framework\Validator\File\IsImage $isImageValidator + * @param Random|null $random * @throws \Magento\Framework\Exception\FileSystemException */ public function __construct( @@ -75,12 +83,15 @@ public function __construct( \Magento\Framework\Filesystem $filesystem, \Magento\Framework\File\Size $fileSize, \Magento\Framework\HTTP\Adapter\FileTransferFactory $httpFactory, - \Magento\Framework\Validator\File\IsImage $isImageValidator + \Magento\Framework\Validator\File\IsImage $isImageValidator, + Random $random = null ) { $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA); $this->filesystem = $filesystem; $this->httpFactory = $httpFactory; $this->isImageValidator = $isImageValidator; + $this->random = $random + ?? ObjectManager::getInstance()->get(Random::class); parent::__construct($scopeConfig, $filesystem, $fileSize); } @@ -147,8 +158,6 @@ public function validate($processingParams, $option) $userValue = []; if ($upload->isUploaded($file) && $upload->isValid($file)) { - $extension = pathinfo(strtolower($fileInfo['name']), PATHINFO_EXTENSION); - $fileName = \Magento\MediaStorage\Model\File\Uploader::getCorrectFileName($fileInfo['name']); $dispersion = \Magento\MediaStorage\Model\File\Uploader::getDispersionPath($fileName); @@ -156,7 +165,8 @@ public function validate($processingParams, $option) $tmpDirectory = $this->filesystem->getDirectoryRead(DirectoryList::SYS_TMP); $fileHash = md5($tmpDirectory->readFile($tmpDirectory->getRelativePath($fileInfo['tmp_name']))); - $filePath .= '/' . $fileHash . '.' . $extension; + $fileRandomName = $this->random->getRandomString(32); + $filePath .= '/' .$fileRandomName; $fileFullPath = $this->mediaDirectory->getAbsolutePath($this->quotePath . $filePath); $upload->addFilter(new \Zend_Filter_File_Rename(['target' => $fileFullPath, 'overwrite' => true])); diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php index 30c3de932c3e6..b9946b99c5fdc 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php @@ -6,6 +6,9 @@ namespace Magento\Catalog\Model\Product\Option\Type\File; +/** + * Validator for existing files. + */ class ValidatorInfo extends Validator { /** @@ -90,7 +93,7 @@ public function validate($optionValue, $option) } $result = false; - if ($validatorChain->isValid($this->fileFullPath)) { + if ($validatorChain->isValid($this->fileFullPath, $optionValue['title'])) { $result = $this->rootDirectory->isReadable($this->fileRelativePath) && isset($optionValue['secret_key']) && $this->buildSecretKey($this->fileRelativePath) == $optionValue['secret_key']; diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php index ee508e30cc93e..73bbc9cc88d3d 100644 --- a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php +++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php @@ -168,6 +168,6 @@ protected function isInRange($value, array $range) */ protected function isNegative($value) { - return intval($value) < 0; + return (int)$value < 0; } } diff --git a/app/code/Magento/Catalog/Model/Product/PriceModifier.php b/app/code/Magento/Catalog/Model/Product/PriceModifier.php index 4d81000501cff..ed9b3597c0dc9 100644 --- a/app/code/Magento/Catalog/Model/Product/PriceModifier.php +++ b/app/code/Magento/Catalog/Model/Product/PriceModifier.php @@ -46,11 +46,11 @@ public function removeTierPrice(\Magento\Catalog\Model\Product $product, $custom foreach ($prices as $key => $tierPrice) { if ($customerGroupId == 'all' && $tierPrice['price_qty'] == $qty - && $tierPrice['all_groups'] == 1 && intval($tierPrice['website_id']) === intval($websiteId) + && $tierPrice['all_groups'] == 1 && (int)$tierPrice['website_id'] === (int)$websiteId ) { unset($prices[$key]); } elseif ($tierPrice['price_qty'] == $qty && $tierPrice['cust_group'] == $customerGroupId - && intval($tierPrice['website_id']) === intval($websiteId) + && (int)$tierPrice['website_id'] === (int)$websiteId ) { unset($prices[$key]); } diff --git a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php index 7cecd2f37bb84..18dc49a852a94 100644 --- a/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php +++ b/app/code/Magento/Catalog/Model/Product/TierPriceManagement.php @@ -181,7 +181,7 @@ public function getList($sku, $customerGroupId) $prices = []; foreach ($product->getData('tier_price') as $price) { - if ((is_numeric($customerGroupId) && intval($price['cust_group']) === intval($customerGroupId)) + if ((is_numeric($customerGroupId) && (int)$price['cust_group'] === (int)$customerGroupId) || ($customerGroupId === 'all' && $price['all_groups']) ) { /** @var \Magento\Catalog\Api\Data\ProductTierPriceInterface $tierPrice */ diff --git a/app/code/Magento/Catalog/Model/Product/Type.php b/app/code/Magento/Catalog/Model/Product/Type.php index 7be199884be1f..0bc5216d2d238 100644 --- a/app/code/Magento/Catalog/Model/Product/Type.php +++ b/app/code/Magento/Catalog/Model/Product/Type.php @@ -285,7 +285,7 @@ public function getTypesByPriority() $types = $this->getTypes(); foreach ($types as $typeId => $typeInfo) { - $priority = isset($typeInfo['index_priority']) ? abs(intval($typeInfo['index_priority'])) : 0; + $priority = isset($typeInfo['index_priority']) ? abs((int)$typeInfo['index_priority']) : 0; if (!empty($typeInfo['composite'])) { $compositePriority[$typeId] = $priority; } else { diff --git a/app/code/Magento/Catalog/Model/ProductCategoryList.php b/app/code/Magento/Catalog/Model/ProductCategoryList.php index 68d2c0606e567..f65d9b4c0bf7a 100644 --- a/app/code/Magento/Catalog/Model/ProductCategoryList.php +++ b/app/code/Magento/Catalog/Model/ProductCategoryList.php @@ -81,7 +81,10 @@ public function getCategoryIds($productId) Select::SQL_UNION_ALL ); - $this->categoryIdList[$productId] = $this->productResource->getConnection()->fetchCol($unionSelect); + $this->categoryIdList[$productId] = array_map( + 'intval', + $this->productResource->getConnection()->fetchCol($unionSelect) + ); } return $this->categoryIdList[$productId]; diff --git a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php index b635d854c9e2b..d397dc515db61 100644 --- a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php +++ b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php @@ -47,22 +47,20 @@ public function getCollection(\Magento\Catalog\Model\Product $product, $type) $products = $this->providers[$type]->getLinkedProducts($product); $converter = $this->converterPool->getConverter($type); - $output = []; $sorterItems = []; foreach ($products as $item) { - $output[$item->getId()] = $converter->convert($item); + $itemId = $item->getId(); + $sorterItems[$itemId] = $converter->convert($item); + $sorterItems[$itemId]['position'] = $sorterItems[$itemId]['position'] ?? 0; } - foreach ($output as $item) { - $itemPosition = $item['position']; - if (!isset($sorterItems[$itemPosition])) { - $sorterItems[$itemPosition] = $item; - } else { - $newPosition = $itemPosition + 1; - $sorterItems[$newPosition] = $item; - } - } - ksort($sorterItems); + usort($sorterItems, function ($itemA, $itemB) { + $posA = intval($itemA['position']); + $posB = intval($itemB['position']); + + return $posA <=> $posB; + }); + return $sorterItems; } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index 1f3b2642953c8..56b8a19d14255 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -666,7 +666,7 @@ public function getProductCount($category) $bind = ['category_id' => (int)$category->getId()]; $counts = $this->getConnection()->fetchOne($select, $bind); - return intval($counts); + return (int)$counts; } /** @@ -920,7 +920,7 @@ public function changeParent( $childrenCount = $this->getChildrenCount($category->getId()) + 1; $table = $this->getEntityTable(); $connection = $this->getConnection(); - $levelFiled = $connection->quoteIdentifier('level'); + $levelField = $connection->quoteIdentifier('level'); $pathField = $connection->quoteIdentifier('path'); /** @@ -960,7 +960,7 @@ public function changeParent( $newPath . '/' ) . ')' ), - 'level' => new \Zend_Db_Expr($levelFiled . ' + ' . $levelDisposition) + 'level' => new \Zend_Db_Expr($levelField . ' + ' . $levelDisposition) ], [$pathField . ' LIKE ?' => $category->getPath() . '/%'] ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php index 01e4b072b0367..9db2c8248ce52 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php @@ -173,7 +173,7 @@ public function getMainTable() public function getMainStoreTable($storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID) { if (is_string($storeId)) { - $storeId = intval($storeId); + $storeId = (int) $storeId; } if ($storeId) { diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php index d8243c05dc7ba..f9ec6500b1635 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php @@ -462,7 +462,7 @@ protected function _isOriginalIndexable() $backendType = $this->getOrigData('backend_type'); $frontendInput = $this->getOrigData('frontend_input'); - if ($backendType == 'int' && $frontendInput == 'select') { + if ($backendType == 'int' && ($frontendInput == 'select' || $frontendInput == 'boolean')) { return true; } elseif ($backendType == 'varchar' && $frontendInput == 'multiselect') { return true; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index b131f78ad3b5c..a00e39b32c4dc 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -476,8 +476,7 @@ public function getFlatState() } /** - * Retrieve is flat enabled flag - * Return always false if magento run admin + * Retrieve is flat enabled. Return always false if magento run admin. * * @return bool */ @@ -505,8 +504,7 @@ protected function _construct() } /** - * Standard resource collection initialization - * Needed for child classes + * Standard resource collection initialization. Needed for child classes. * * @param string $model * @param string $entityModel @@ -545,8 +543,7 @@ protected function _prepareStaticFields() } /** - * Retrieve collection empty item - * Redeclared for specifying id field name without getting resource model inside model + * Get collection empty item. Redeclared for specifying id field name without getting resource model inside model. * * @return \Magento\Framework\DataObject */ @@ -632,8 +629,7 @@ public function _loadAttributes($printQuery = false, $logQuery = false) } /** - * Add attribute to entities in collection - * If $attribute=='*' select all attributes + * Add attribute to entities in collection. If $attribute=='*' select all attributes. * * @param array|string|integer|\Magento\Framework\App\Config\Element $attribute * @param bool|string $joinType @@ -669,8 +665,7 @@ public function addAttributeToSelect($attribute, $joinType = false) } /** - * Processing collection items after loading - * Adding url rewrites, minimal prices, final prices, tax percents + * Processing collection items after loading. Adding url rewrites, minimal prices, final prices, tax percents. * * @return $this */ @@ -681,6 +676,7 @@ protected function _afterLoad() } $this->_prepareUrlDataObject(); + $this->prepareStoreId(); if (count($this)) { $this->_eventManager->dispatch('catalog_product_collection_load_after', ['collection' => $this]); @@ -689,6 +685,23 @@ protected function _afterLoad() return $this; } + /** + * Add Store ID to products from collection. + * + * @return $this + */ + protected function prepareStoreId() + { + if ($this->getStoreId() !== null) { + /** @var $item \Magento\Catalog\Model\Product */ + foreach ($this->_items as $item) { + $item->setStoreId($this->getStoreId()); + } + } + + return $this; + } + /** * Prepare Url Data object * @@ -755,8 +768,7 @@ public function addIdFilter($productId, $exclude = false) } /** - * Adding product website names to result collection - * Add for each product websites information + * Adding product website names to result collection. Add for each product websites information. * * @return $this */ @@ -767,7 +779,7 @@ public function addWebsiteNamesToResult() } /** - * {@inheritdoc} + * @inheritdoc */ public function load($printQuery = false, $logQuery = false) { @@ -825,8 +837,7 @@ protected function doAddWebsiteNamesToResult() } /** - * Add store availability filter. Include availability product - * for store website + * Add store availability filter. Include availability product for store website. * * @param null|string|bool|int|Store $store * @return $this @@ -1115,7 +1126,7 @@ public function getSelectCountSql() /** * Get SQL for get record count * - * @param null $select + * @param \Magento\Framework\DB\Select $select * @param bool $resetLeftJoins * @return \Magento\Framework\DB\Select */ @@ -1357,8 +1368,7 @@ public function joinUrlRewrite() } /** - * Add URL rewrites data to product - * If collection loadded - run processing else set flag + * Add URL rewrites data to product. If collection loadded - run processing else set flag. * * @param int|string $categoryId * @return $this @@ -1581,7 +1591,8 @@ public function addAttributeToFilter($attribute, $condition = null, $joinType = } /** - * {@inheritdoc} + * @inheritdoc + * * @since 101.0.0 */ protected function getEntityPkName(\Magento\Eav\Model\Entity\AbstractEntity $entity) @@ -1792,7 +1803,8 @@ protected function _productLimitationJoinWebsite() } $conditions[] = $this->getConnection()->quoteInto( 'product_website.website_id IN(?)', - $filters['website_ids'] + $filters['website_ids'], + 'int' ); } elseif (isset( $filters['store_id'] @@ -1804,7 +1816,7 @@ protected function _productLimitationJoinWebsite() ) { $joinWebsite = true; $websiteId = $this->_storeManager->getStore($filters['store_id'])->getWebsiteId(); - $conditions[] = $this->getConnection()->quoteInto('product_website.website_id = ?', $websiteId); + $conditions[] = $this->getConnection()->quoteInto('product_website.website_id = ?', $websiteId, 'int'); } $fromPart = $this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM); @@ -2000,12 +2012,12 @@ protected function _applyProductLimitations() $conditions = [ 'cat_index.product_id=e.entity_id', - $this->getConnection()->quoteInto('cat_index.store_id=?', $filters['store_id']), + $this->getConnection()->quoteInto('cat_index.store_id=?', $filters['store_id'], 'int'), ]; if (isset($filters['visibility']) && !isset($filters['store_table'])) { - $conditions[] = $this->getConnection()->quoteInto('cat_index.visibility IN(?)', $filters['visibility']); + $conditions[] = $this->getConnection()->quoteInto('cat_index.visibility IN(?)', $filters['visibility'], 'int'); } - $conditions[] = $this->getConnection()->quoteInto('cat_index.category_id=?', $filters['category_id']); + $conditions[] = $this->getConnection()->quoteInto('cat_index.category_id=?', $filters['category_id'], 'int'); if (isset($filters['category_is_anchor'])) { $conditions[] = $this->getConnection()->quoteInto('cat_index.is_parent=?', $filters['category_is_anchor']); } @@ -2343,7 +2355,10 @@ private function getGalleryReadHandler() } /** + * Retrieve Media gallery resource. + * * @deprecated 101.0.1 + * * @return \Magento\Catalog\Model\ResourceModel\Product\Gallery */ private function getMediaGalleryResource() diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php index eacc1240964a4..b68c43e40ff2f 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php @@ -160,9 +160,8 @@ protected function createBaseLoadSelect($entityId, $storeId, $attributeId) public function createBatchBaseSelect($storeId, $attributeId) { $linkField = $this->metadata->getLinkField(); - $conn = $this->getConnection(); - $positionCheckSql = $conn->getCheckSql( + $positionCheckSql = $this->getConnection()->getCheckSql( 'value.position IS NULL', 'default_value.position', 'value.position' @@ -170,64 +169,54 @@ public function createBatchBaseSelect($storeId, $attributeId) $mainTableAlias = $this->getMainTableAlias(); - $storeCondition = $conn->quoteInto('value.store_id = ?', (int)$storeId); - $defStoreCondition = $conn->quoteInto('default_value.store_id = ?', Store::DEFAULT_STORE_ID); - $select = $conn->select() - ->from( - [$mainTableAlias => $this->getMainTable()], + $select = $this->getConnection()->select()->from( + [$mainTableAlias => $this->getMainTable()], + [ + 'value_id', + 'file' => 'value', + 'media_type' + ] + )->joinInner( + ['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)], + $mainTableAlias . '.value_id = entity.value_id', + [$linkField] + )->joinLeft( + ['value' => $this->getTable(self::GALLERY_VALUE_TABLE)], + implode( + ' AND ', [ - 'value_id', - 'file' => 'value', - 'media_type' + $mainTableAlias . '.value_id = value.value_id', + $this->getConnection()->quoteInto('value.store_id = ?', (int)$storeId), + 'value.' . $linkField . ' = entity.' . $linkField, ] - ) - ->joinInner( - ['entity' => $this->getTable(self::GALLERY_VALUE_TO_ENTITY_TABLE)], - $mainTableAlias . '.value_id = entity.value_id', - [$linkField] - ) - ->joinLeft( - ['value' => $this->getTable(self::GALLERY_VALUE_TABLE)], - implode( - ' AND ', - [ - $mainTableAlias . '.value_id = value.value_id', - $storeCondition, - 'value.' . $linkField . ' = entity.' . $linkField, - ] - ), - [] - ) - ->joinLeft( - ['default_value' => $this->getTable(self::GALLERY_VALUE_TABLE)], - implode( - ' AND ', - [ - $mainTableAlias . '.value_id = default_value.value_id', - $defStoreCondition, - 'default_value.' . $linkField . ' = entity.' . $linkField, - ] - ), - [] - ) - ->columns([ - 'label' => $conn->getIfNullSql('`value`.`label`', '`default_value`.`label`'), - 'position' => $conn->getIfNullSql('`value`.`position`', '`default_value`.`position`'), - 'disabled' => $conn->getIfNullSql('`value`.`disabled`', '`default_value`.`disabled`'), - 'label_default' => 'default_value.label', - 'position_default' => 'default_value.position', - 'disabled_default' => 'default_value.disabled' - ]) - ->where($mainTableAlias . '.attribute_id = ?', $attributeId) - ->where($mainTableAlias . '.disabled = 0'); - - // filter entities by store - if ($storeId > 0) { - $orWhere = $storeCondition . ' OR '. $defStoreCondition; - $select->where($orWhere); - } - - $select->order($positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC); + ), + [] + )->joinLeft( + ['default_value' => $this->getTable(self::GALLERY_VALUE_TABLE)], + implode( + ' AND ', + [ + $mainTableAlias . '.value_id = default_value.value_id', + $this->getConnection()->quoteInto('default_value.store_id = ?', Store::DEFAULT_STORE_ID), + 'default_value.' . $linkField . ' = entity.' . $linkField, + ] + ), + [] + )->columns([ + 'label' => $this->getConnection()->getIfNullSql('`value`.`label`', '`default_value`.`label`'), + 'position' => $this->getConnection()->getIfNullSql('`value`.`position`', '`default_value`.`position`'), + 'disabled' => $this->getConnection()->getIfNullSql('`value`.`disabled`', '`default_value`.`disabled`'), + 'label_default' => 'default_value.label', + 'position_default' => 'default_value.position', + 'disabled_default' => 'default_value.disabled' + ])->where( + $mainTableAlias . '.attribute_id = ?', + $attributeId + )->where( + $mainTableAlias . '.disabled = 0' + )->order( + $positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC + ); return $select; } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php index 5b68730209b40..77836c58d5070 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Source.php @@ -84,7 +84,7 @@ protected function _getIndexableAttributes($multiSelect) if ($multiSelect == true) { $select->where('ea.backend_type = ?', 'varchar')->where('ea.frontend_input = ?', 'multiselect'); } else { - $select->where('ea.backend_type = ?', 'int')->where('ea.frontend_input = ?', 'select'); + $select->where('ea.backend_type = ?', 'int')->where('ea.frontend_input IN( ? )', ['select', 'boolean']); } return $this->getConnection()->fetchCol($select); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php index 8428ff3688b28..0005ac8dea58a 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php @@ -190,7 +190,7 @@ public function getQuery(array $dimensions, string $productType, array $entityId $specialFromExpr = "{$specialFrom} IS NULL OR {$specialFromDate} <= {$currentDate}"; $specialToExpr = "{$specialTo} IS NULL OR {$specialToDate} >= {$currentDate}"; $specialPriceExpr = $connection->getCheckSql( - "{$specialPrice} IS NOT NULL AND {$specialFromExpr} AND {$specialToExpr}", + "{$specialPrice} IS NOT NULL AND ({$specialFromExpr}) AND ({$specialToExpr})", $specialPrice, $maxUnsignedBigint ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php index ce0a9b6e461ce..5ffc9fbd575b6 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php @@ -160,19 +160,22 @@ protected function _saveValuePrices(AbstractModel $object) && isset($objectPrice) && $object->getStoreId() != Store::DEFAULT_STORE_ID ) { - $baseCurrency = $this->_config->getValue( + $website = $this->_storeManager->getStore($object->getStoreId())->getWebsite(); + + $websiteBaseCurrency = $this->_config->getValue( Currency::XML_PATH_CURRENCY_BASE, - 'default' + ScopeInterface::SCOPE_WEBSITE, + $website ); - $storeIds = $this->_storeManager->getStore($object->getStoreId())->getWebsite()->getStoreIds(); + $storeIds = $website->getStoreIds(); if (is_array($storeIds)) { foreach ($storeIds as $storeId) { if ($priceType == 'fixed') { $storeCurrency = $this->_storeManager->getStore($storeId)->getBaseCurrencyCode(); /** @var $currencyModel Currency */ $currencyModel = $this->_currencyFactory->create(); - $currencyModel->load($baseCurrency); + $currencyModel->load($websiteBaseCurrency); $rate = $currencyModel->getRate($storeCurrency); if (!$rate) { $rate = 1; diff --git a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php index 54a13be864db7..77368517a3155 100644 --- a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php +++ b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php @@ -30,7 +30,7 @@ public function getValue() $this->value = false; foreach ($this->priceInfo->getPrices() as $price) { if ($price instanceof BasePriceProviderInterface && $price->getValue() !== false) { - $this->value = min($price->getValue(), $this->value ?: $price->getValue()); + $this->value = min($price->getValue(), $this->value !== false ? $this->value: $price->getValue()); } } } diff --git a/app/code/Magento/Catalog/Setup/UpgradeData.php b/app/code/Magento/Catalog/Setup/UpgradeData.php index e8d8582b07f96..4e0be7396a166 100644 --- a/app/code/Magento/Catalog/Setup/UpgradeData.php +++ b/app/code/Magento/Catalog/Setup/UpgradeData.php @@ -5,7 +5,6 @@ */ namespace Magento\Catalog\Setup; -use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration; use Magento\Eav\Setup\EavSetup; use Magento\Framework\Setup\ModuleContextInterface; use Magento\Framework\Setup\ModuleDataSetupInterface; @@ -397,10 +396,6 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $this->enableSegmentation($setup); } - if (version_compare($context->getVersion(), '2.2.6') < 0) { - $this->savePriceIndexerDimensionsMode($setup); - } - $setup->endSetup(); } @@ -485,21 +480,4 @@ private function enableSegmentation(ModuleDataSetupInterface $setup) $setup->getConnection()->truncateTable($setup->getTable('catalog_category_product_index_replica')); $setup->getConnection()->truncateTable($setup->getTable('catalog_category_product_index_tmp')); } - - /** - * @param ModuleDataSetupInterface $setup - * @return void - */ - private function savePriceIndexerDimensionsMode(ModuleDataSetupInterface $setup) - { - $setup->getConnection()->insert( - $setup->getTable('core_config_data'), - [ - 'scope' => 'default', - 'scope_id' => 0, - 'path' => \Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcher::XML_PATH_PRICE_DIMENSIONS_MODE, - 'value' => DimensionModeConfiguration::DIMENSION_NONE - ] - ); - } } diff --git a/app/code/Magento/Catalog/Setup/UpgradeWebsiteAttributes.php b/app/code/Magento/Catalog/Setup/UpgradeWebsiteAttributes.php index 3d300d9c849a9..05e4bb3817beb 100644 --- a/app/code/Magento/Catalog/Setup/UpgradeWebsiteAttributes.php +++ b/app/code/Magento/Catalog/Setup/UpgradeWebsiteAttributes.php @@ -148,6 +148,20 @@ private function processAttributeValues(ModuleDataSetupInterface $setup, array $ */ private function fetchAttributeValues(ModuleDataSetupInterface $setup, $tableName) { + $multipleStoresInWebsite = array_values( + array_reduce( + array_filter($this->getGroupedStoreViews($setup), function ($storeViews) { + return is_array($storeViews) && count($storeViews) > 1; + }), + 'array_merge', + [] + ) + ); + + if (count($multipleStoresInWebsite) < 1) { + return []; + } + $connection = $setup->getConnection(); $batchSelectIterator = $this->batchQueryGenerator->generate( 'value_id', @@ -158,27 +172,18 @@ private function fetchAttributeValues(ModuleDataSetupInterface $setup, $tableNam '*' ) ->join( - [ - 'cea' => $setup->getTable('catalog_eav_attribute'), - ], + ['cea' => $setup->getTable('catalog_eav_attribute')], 'cpei.attribute_id = cea.attribute_id', '' ) ->join( - [ - 'st' => $setup->getTable('store'), - ], + ['st' => $setup->getTable('store')], 'st.store_id = cpei.store_id', 'st.website_id' ) - ->where( - 'cea.is_global = ?', - self::ATTRIBUTE_WEBSITE - ) - ->where( - 'cpei.store_id <> ?', - self::GLOBAL_STORE_VIEW_ID - ) + ->where('cea.is_global = ?', self::ATTRIBUTE_WEBSITE) + ->where('cpei.store_id IN (?)', $multipleStoresInWebsite), + 1000 ); foreach ($batchSelectIterator as $select) { @@ -201,17 +206,15 @@ private function getGroupedStoreViews(ModuleDataSetupInterface $setup) ->select() ->from( $setup->getTable('store'), - '*' - ); + ['store_id', 'website_id'] + )->where('store_id <> ?', self::GLOBAL_STORE_VIEW_ID); - $storeViews = $connection->fetchAll($query); + $storeViews = $connection->fetchPairs($query); $this->groupedStoreViews = []; - foreach ($storeViews as $storeView) { - if ($storeView['store_id'] != 0) { - $this->groupedStoreViews[$storeView['website_id']][] = $storeView['store_id']; - } + foreach ($storeViews as $storeId => $websiteId) { + $this->groupedStoreViews[$websiteId][] = $storeId; } return $this->groupedStoreViews; diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml index b21f29ac79e05..7dafeff34a2ea 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml @@ -22,9 +22,7 @@ <argument name="productName"/> </arguments> <click selector="{{StorefrontProductPageSection.addToCartBtn}}" stepKey="addToCart"/> - <waitForElementVisible selector="{{StorefrontProductPageSection.addToCartButtonTitleIsAdding}}" stepKey="waitForElementVisibleAddToCartButtonTitleIsAdding"/> <waitForElementNotVisible selector="{{StorefrontProductPageSection.addToCartButtonTitleIsAdding}}" stepKey="waitForElementNotVisibleAddToCartButtonTitleIsAdding"/> - <waitForElementVisible selector="{{StorefrontProductPageSection.addToCartButtonTitleIsAdded}}" stepKey="waitForElementVisibleAddToCartButtonTitleIsAdded"/> <waitForElementNotVisible selector="{{StorefrontProductPageSection.addToCartButtonTitleIsAdded}}" stepKey="waitForElementNotVisibleAddToCartButtonTitleIsAdded"/> <waitForElementVisible selector="{{StorefrontProductPageSection.addToCartButtonTitleIsAddToCart}}" stepKey="waitForElementVisibleAddToCartButtonTitleIsAddToCart"/> <waitForPageLoad stepKey="waitForPageLoad"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryProductAttributeActionGroup.xml index b1300f63d967c..5c71342264212 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryProductAttributeActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryProductAttributeActionGroup.xml @@ -25,4 +25,17 @@ <waitForElementVisible selector="{{AdminConfirmationModalSection.message}}" stepKey="waitingForWarningModal"/> <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="confirmStoreDelete"/> </actionGroup> + + <actionGroup name="navigateToProductAttribute"> + <arguments> + <argument name="attributeCode" type="string"/> + </arguments> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <waitForPageLoad stepKey="waitForAttributeGridPageLoad"/> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingFilters"/> + <fillField selector="{{AdminProductAttributeGridSection.filterByAttributeCode}}" userInput="{{attributeCode}}" stepKey="fillAttributeCodeFilter"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickApplyFiltersButton"/> + <click selector="{{AdminProductAttributeGridSection.attributeCode(attributeCode)}}" stepKey="navigateToAttributeEditPage" /> + <waitForPageLoad stepKey="waitForAttributeEditPageLoad" /> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml index ee7e3c82564b7..1c4e7a8df2436 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml @@ -90,4 +90,83 @@ <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDone"/> <waitForElementNotVisible selector="{{AdminProductFormAdvancedPricingSection.specialPrice}}" stepKey="waitForCloseModalWindow"/> </actionGroup> + + <!--Set product to website--> + <actionGroup name="ProductSetWebsite"> + <arguments> + <argument name="website"/> + </arguments> + <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="scrollToWebsites"/> + <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="clickToOpenProductInWebsite"/> + <waitForPageLoad stepKey="waitForPageOpened"/> + <click selector="{{ProductInWebsitesSection.website(website.name)}}" stepKey="selectWebsite"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + <waitForPageLoad time='60' stepKey="waitForPageOpened1"/> + </actionGroup> + + <actionGroup name="ProductSetAdvancedPricing"> + <arguments> + <argument name="website"/> + <argument name="group" type="string" defaultValue="Retailer"/> + <argument name="quantity" type="string" defaultValue="1"/> + <argument name="price" type="string" defaultValue="Discount"/> + <argument name="amount" type="string" defaultValue="45"/> + </arguments> + <scrollToTopOfPage stepKey="scrollToTopOfThePagePreventHeaderOverlap"/> + <click selector="{{AdminProductFormSection.advancedPricingLink}}" stepKey="clickOnAdvancedPricingButton"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="waitForCustomerGroupPriceAddButton"/> + <click selector="{{AdminProductFormAdvancedPricingSection.customerGroupPriceAddButton}}" stepKey="addCustomerGroupAllGroupsQty1PriceDiscountAnd10percent"/> + <waitForElement selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" stepKey="waitForSelectCustomerGroupNameAttribute"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceWebsiteSelect('0')}}" userInput="{{website.name}}" stepKey="selectProductWebsiteValue"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceCustGroupSelect('0')}}" userInput="{{group}}" stepKey="selectProductCustomGroupValue"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPriceQtyInput('0')}}" userInput="{{quantity}}" stepKey="fillProductTierPriceQtyInput"/> + <selectOption selector="{{AdminProductFormAdvancedPricingSection.productTierPriceValueTypeSelect('0')}}" userInput="{{price}}" stepKey="selectProductTierPriceValueType"/> + <fillField selector="{{AdminProductFormAdvancedPricingSection.productTierPricePercentageValuePriceInput('0')}}" userInput="{{amount}}" stepKey="selectProductTierPricePriceInput"/> + <click selector="{{AdminProductFormAdvancedPricingSection.doneButton}}" stepKey="clickDoneButton"/> + <waitForPageLoad stepKey="waitForProductSave"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + <waitForPageLoad time="60" stepKey="waitForProductSave1"/> + <see userInput="You saved the product." stepKey="seeSaveConfirmation"/> + </actionGroup> + + <!--Switch to New Store view--> + <actionGroup name="SwitchToTheNewStoreView"> + <arguments> + <argument name="storeViewName"/> + </arguments> + <scrollTo selector="{{AdminHeaderSection.pageTitle}}" stepKey="scrollToUp"/> + <waitForElementVisible selector="{{AdminProductFormActionSection.changeStoreButton}}" stepKey="waitForElementBecomeVisible"/> + <click selector="{{AdminProductFormActionSection.changeStoreButton}}" stepKey="clickStoreviewSwitcher"/> + <click selector="{{AdminProductFormActionSection.selectStoreView(storeViewName.name)}}" stepKey="chooseStoreView"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="acceptStoreSwitchingMessage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> + + <!--Select Product In Websites--> + <actionGroup name="SelectProductInWebsitesActionGroup"> + <arguments> + <argument name="website" type="string"/> + </arguments> + <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="scrollToWebsites"/> + <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="clickToOpenProductInWebsite"/> + <waitForPageLoad stepKey="waitForPageOpened"/> + <checkOption selector="{{ProductInWebsitesSection.website(website)}}" stepKey="selectWebsite"/> + </actionGroup> + + <!--Remove product image--> + <actionGroup name="RemoveProductImage"> + <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageUploadButton}}" visible="false" stepKey="openProductImagesSection"/> + <waitForPageLoad time="30" stepKey="waitForPageRefresh"/> + <click selector="{{AdminProductImagesSection.removeImageButton}}" stepKey="clickRemoveImage"/> + </actionGroup> + + <!-- Assert no product image in Admin Product page --> + <actionGroup name="AssertProductImageNotInAdminProductPage"> + <arguments> + <argument name="image" defaultValue="MagentoLogo" /> + </arguments> + <conditionalClick selector="{{AdminProductImagesSection.productImagesToggle}}" dependentSelector="{{AdminProductImagesSection.imageUploadButton}}" visible="false" stepKey="openProductImagesSection"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <dontSeeElement selector="{{AdminProductImagesSection.imageFile(image.filename)}}" stepKey="seeImage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml new file mode 100644 index 0000000000000..341360ce87173 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="navigateToEditProductAttribute"> + <arguments> + <argument name="attributeLabel" type="string"/> + </arguments> + <amOnPage url="{{AdminProductAttributeGridPage.url}}" stepKey="navigateToProductAttributeGrid"/> + <fillField selector="{{AdminProductAttributeGridSection.gridFilterFrontEndLabel}}" userInput="{{attributeLabel}}" stepKey="navigateToAttributeEditPage1" /> + <click selector="{{AdminProductAttributeGridSection.search}}" stepKey="navigateToAttributeEditPage2" /> + <click selector="{{AdminProductAttributeGridSection.firstRow}}" stepKey="navigateToAttributeEditPage3" /> + </actionGroup> + <actionGroup name="changeUseForPromoRuleConditionsProductAttribute"> + <arguments> + <argument name="useForPromoRule" type="string" defaultValue="Yes"/> + </arguments> + <click selector="{{StorefrontPropertiesSection.storefrontPropertiesTab}}" stepKey="clickStoreFrontPropertiesTab"/> + <selectOption selector="{{StorefrontPropertiesSection.useForPromoRuleConditions}}" userInput="{{useForPromoRule}}" stepKey="changeOption"/> + <click selector="{{AttributePropertiesSection.save}}" stepKey="saveAttribute"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You saved the product attribute." stepKey="successMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml new file mode 100644 index 0000000000000..fc2a8964ea302 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKey"> + <arguments> + <argument name="product"/> + </arguments> + <!-- Go to storefront product page, assert product name and sku --> + <amOnPage url="{{product.custom_attributes[url_key]}}.html" stepKey="navigateToProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <seeInTitle userInput="{{product.name}}" stepKey="assertProductNameTitle"/> + <see userInput="{{product.name}}" selector="{{StorefrontProductInfoMainSection.productName}}" stepKey="assertProductName"/> + <see userInput="{{product.sku}}" selector="{{StorefrontProductInfoMainSection.productSku}}" stepKey="assertProductSku"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml index 12f4b5f117dee..ebeee87b1c89e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml @@ -13,7 +13,7 @@ <argument name="product" defaultValue="product"/> </arguments> <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> - <waitForPageLoad time="30" stepKey="waitForProductsPageToLoad"/> + <waitForPageLoad time="60" stepKey="waitForProductsPageToLoad"/> <click stepKey="openFiltersSectionOnProductsPage" selector="{{AdminProductFiltersSection.FiltersButton}}"/> <conditionalClick selector="{{AdminProductFiltersSection.clearFiltersButton}}" dependentSelector="{{AdminProductFiltersSection.clearFiltersButton}}" visible="true" stepKey="cleanFiltersIfTheySet"/> <fillField stepKey="fillSkuFieldOnFiltersSection" userInput="{{product.sku}}" selector="{{AdminProductFiltersSection.SkuInput}}"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml index 1f9bd218b1111..8f0df2fc899a9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Check the category page --> <actionGroup name="StorefrontCheckCategoryActionGroup"> <arguments> @@ -35,4 +35,16 @@ <click selector="{{StorefrontCategoryMainSection.modeListButton}}" stepKey="switchCategoryViewToListMode"/> <seeElement selector="{{StorefrontCategoryMainSection.categoryTitle}}" stepKey="assertCategoryTitle"/> </actionGroup> + + <!-- Go to storefront category product page by given parameters --> + <actionGroup name="GoToStorefrontCategoryPageByParameters"> + <arguments> + <argument name="category" type="string"/> + <argument name="mode" type="string"/> + <argument name="sortBy" type="string" defaultValue="position"/> + <argument name="sort" type="string" defaultValue="asc"/> + </arguments> + <!-- Go to storefront category page --> + <amOnPage url="{{StorefrontCategoryPage.url(category)}}?product_list_mode={{mode}}&product_list_order={{sortBy}}&product_list_dir={{sort}}" stepKey="onCategoryPage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeSetData.xml new file mode 100644 index 0000000000000..67e71cc3b9e92 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogAttributeSetData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="CatalogAttributeSet" type="CatalogAttributeSet"> + <data key="attribute_set_name" unique="suffix">test_set_</data> + <data key="attributeGroupId">7</data> + <data key="skeletonId">4</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml new file mode 100644 index 0000000000000..8d460fb7cbf1d --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogPriceData.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="CatalogPriceScopeWebsite" type="catalog_price_config_state"> + <requiredEntity type="scope">ScopeWebsite</requiredEntity> + <requiredEntity type="default_product_price">DefaultProductPrice</requiredEntity> + </entity> + <entity name="ScopeWebsite" type="scope"> + <data key="value">1</data> + </entity> + <entity name="DefaultProductPrice" type="default_product_price"> + <data key="value">0</data> + </entity> + <entity name="DefaultConfigCatalogPrice" type="catalog_price_config_state"> + <requiredEntity type="scope">ScopeGlobal</requiredEntity> + <requiredEntity type="default_product_price">DefaultProductPrice</requiredEntity> + </entity> + <entity name="ScopeGlobal" type="scope"> + <data key="value">0</data> + </entity> + <entity name="DefaultProductPrice" type="default_product_price"> + <data key="value"/> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml index 2423383bc19f7..77cb6e13a95e3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml @@ -7,9 +7,13 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ProductAttributeFrontendLabel" type="FrontendLabel"> <data key="store_id">0</data> <data key="label" unique="suffix">attribute</data> </entity> + <entity name="ProductAttributeFrontendLabelThree" type="FrontendLabel"> + <data key="store_id">0</data> + <data key="label" unique="suffix">attributeThree</data> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml index 14268fed8ff6b..007529a06d9f4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml @@ -34,6 +34,32 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <entity name="ApiSimpleOne" type="product2"> + <data key="sku" unique="suffix">api-simple-product</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">Api Simple Product</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">api-simple-product</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> + </entity> + <entity name="ApiSimpleTwo" type="product2"> + <data key="sku" unique="suffix">api-simple-product-two</data> + <data key="type_id">simple</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">Api Simple Product Two</data> + <data key="price">234.00</data> + <data key="urlKey" unique="suffix">api-simple-product-two</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute">CustomAttributeProductAttribute</requiredEntity> + </entity> <entity name="ApiSimpleProductUpdateDescription" type="product2"> <requiredEntity type="custom_attribute">ApiProductDescription</requiredEntity> <requiredEntity type="custom_attribute">ApiProductShortDescription</requiredEntity> @@ -223,4 +249,18 @@ <var key="sku" entityType="product" entityKey="sku" /> <requiredEntity type="product_option">ProductOptionDropDownWithLongValuesTitle</requiredEntity> </entity> + <entity name="SimpleProductWithCustomAttributeSet" type="product"> + <data key="sku" unique="suffix">testSku</data> + <data key="type_id">simple</data> + <var key="attribute_set_id" entityKey="attribute_set_id" entityType="CatalogAttributeSet"/> + <data key="visibility">4</data> + <data key="name" unique="suffix">testProductName</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">testurlkey</data> + <data key="status">1</data> + <data key="weight">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml new file mode 100644 index 0000000000000..ae1b5afe4008a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="TestDataTierPrice" type="data"> + <data key="goldenPrice1">$676.50</data> + <data key="goldenPrice2">$615.00</data> + </entity> +</entities> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_attribute_set-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_attribute_set-meta.xml new file mode 100644 index 0000000000000..00a5f8fef18f1 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_attribute_set-meta.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="AddCatalogAttributeToAttributeSet" dataType="CatalogAttributeSet" type="create" auth="adminOauth" url="/V1/products/attribute-sets" method="POST"> + <contentType>application/json</contentType> + <object key="attributeSet" dataType="CatalogAttributeSet"> + <field key="attribute_set_name">string</field> + <field key="sort_order">integer</field> + </object> + <field key="skeletonId">integer</field> + </operation> + <operation name="DeleteCatalogAttributeFromAttributeSet" dataType="CatalogAttributeSet" type="delete" auth="adminOauth" url="/V1/products/attribute-sets/{attribute_set_id}" method="DELETE"> + <contentType>application/json</contentType> + </operation> + <operation name="GetCatalogAttributesFromDefaultSet" dataType="CatalogAttributeSet" type="get" auth="adminOauth" url="/V1/products/attribute-sets/{attribute_set_id}" method="GET"> + <contentType>application/json</contentType> + </operation> +</operations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml new file mode 100644 index 0000000000000..7c57827356242 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_price-meta.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + <operation name="CatalogPriceConfigState" dataType="catalog_price_config_state" type="create" auth="adminFormKey" url="/admin/system_config/save/section/catalog/" method="POST" successRegex="/messages-message-success/"> + <object key="groups" dataType="catalog_price_config_state"> + <object key="price" dataType="catalog_price_config_state"> + <object key="fields" dataType="catalog_price_config_state"> + <object key="scope" dataType="scope"> + <field key="value">string</field> + </object> + <object key="default_product_price" dataType="default_product_price"> + <field key="value">string</field> + </object> + </object> + </object> + </object> + </operation> +</operations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml index 3ac0645fa13d2..1bf7d0b0d988f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml @@ -7,7 +7,7 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProduct" dataType="product" type="create" auth="adminOauth" url="/V1/products" method="POST"> <contentType>application/json</contentType> <object dataType="product" key="product"> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml index 3c90f32990b7d..09544d05a9b56 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml @@ -7,16 +7,16 @@ --> <operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> <operation name="CreateProductLink" dataType="product_link" type="create"> <field key="sku">string</field> <field key="link_type">string</field> <field key="linked_product_sku">string</field> <field key="linked_product_type">string</field> <field key="position">integer</field> - <array key="extension_attributes"> - <value>product_link_extension_attribute</value> - </array> + <object key="extension_attributes" dataType="product_link_extension_attribute"> + <field key="qty">integer</field> + </object> </operation> <operation name="UpdateProductLink" dataType="product_link" type="update"> <field key="sku">string</field> @@ -24,8 +24,8 @@ <field key="linked_product_sku">string</field> <field key="linked_product_type">string</field> <field key="position">integer</field> - <array key="extension_attributes"> - <value>product_link_extension_attribute</value> - </array> + <object key="extension_attributes" dataType="product_link_extension_attribute"> + <field key="qty">integer</field> + </object> </operation> </operations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml new file mode 100644 index 0000000000000..3a8999b523f13 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateProductLinks" dataType="product_links" type="create" auth="adminOauth" url="/V1/products/{sku}/links" method="POST"> + <contentType>application/json</contentType> + <array key="items"> + <value>product_link</value> + </array> + </operation> +</operations> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml index a11d05140c638..45485509f5e3e 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml @@ -14,5 +14,6 @@ <section name="AdminCategoryBasicFieldSection"/> <section name="AdminCategorySEOSection"/> <section name="AdminCategoryModalSection"/> + <section name="AdminCategoryProductsGridSection"/> </page> </pages> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeEditPage.xml index 3d7e79343155b..e7de264dc2b75 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeEditPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeEditPage.xml @@ -11,5 +11,6 @@ <page name="AdminProductAttributeEditPage" url="catalog/product_attribute/edit/" area="admin" module="Magento_Catalog"> <section name="AdminProductAttributeEditSection"/> <section name="AdminConfirmationModalSection"/> + <section name="AdminEditAttributeStorefrontPropertiesSection"/> </page> </pages> diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml index a5de7453d9c23..3a9bd1647a476 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml @@ -9,5 +9,6 @@ xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> <page name="AdminProductAttributeGridPage" url="catalog/product_attribute" area="admin" module="Catalog"> <section name="AdminProductAttributeGridSection"/> + <section name="AdminDataGridHeaderSection"/> </page> </pages> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml new file mode 100644 index 0000000000000..61a4c497d5b3a --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminCategoryProductsGridSection"> + <element name="productGridNameProduct" type="text" selector="//table[@id='catalog_category_products_table']//td[contains(., '{{productName}}')]" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml index e5e37b59af359..e241370220921 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml @@ -7,9 +7,13 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AttributePropertiesSection"> <element name="AdvancedProperties" type="button" selector="#advanced_fieldset-wrapper"/> <element name="Save" type="button" selector="#save"/> </section> + <section name="StorefrontPropertiesSection"> + <element name="storefrontPropertiesTab" selector="#product_attribute_tabs_front" type="button" timeout="30"/> + <element name="useForPromoRuleConditions" type="select" selector="#is_used_for_promo_rules"/> + </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditAttributeStorefrontPropertiesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditAttributeStorefrontPropertiesSection.xml new file mode 100644 index 0000000000000..e430998f1e2be --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminEditAttributeStorefrontPropertiesSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEditAttributeStorefrontPropertiesSection"> + <element name="storeFrontPropertiesTab" selector="#product_attribute_tabs_front" type="button"/> + <element name="useForPromoRuleConditions" type="select" selector="#is_used_for_promo_rules"/> + </section> +</sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml index bb944270a10a9..020ae95b11c2b 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml @@ -14,7 +14,8 @@ <element name="gridFilterFrontEndLabel" type="input" selector="#attributeGrid_filter_frontend_label"/> <element name="search" type="button" selector="button[data-action=grid-filter-apply]" timeout="30"/> <element name="resetFilter" type="button" selector="button[data-action='grid-filter-reset']" timeout="30"/> - <element name="firstRow" type="button" selector="//*[@id='attributeGrid_table']/tbody/tr[1]"/> + <element name="firstRow" type="button" selector="//*[@id='attributeGrid_table']/tbody/tr[1]" timeout="30"/> <element name="attributeCodeFilterInput" type="input" selector=".admin__data-grid-filters input[name='attribute_code']"/> + <element name="filterByAttributeCode" type="input" selector="#attributeGrid_filter_attribute_code"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml index 1042b1e5a5464..0f1fe8abeb3b5 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml @@ -20,6 +20,5 @@ <element name="productTierPricePercentageValuePriceInput" type="input" selector="[name='product[tier_price][{{var1}}][percentage_value]']" parameterized="true"/> <element name="specialPrice" type="input" selector="input[name='product[special_price]']"/> <element name="doneButton" type="button" selector=".product_form_product_form_advanced_pricing_modal button.action-primary" timeout="5"/> - <element name="save" type="button" selector="#save-button"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml index 5f33eb6f52edf..0cbb0cb519751 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml @@ -43,7 +43,7 @@ </section> <section name="ProductWYSIWYGSection"> <element name="Switcher" type="button" selector="//select[@id='dropdown-switcher']"/> - <element name="v4" type ="button" selector="//select[@id='dropdown-switcher']/option[text()='TinyMCE 4.3.6']" /> + <element name="v436" type ="button" selector="//select[@id='dropdown-switcher']/option[text()='TinyMCE 4.3.6']" /> <element name="v3" type ="button" selector="//select[@id='dropdown-switcher']/option[text()='TinyMCE 3.6(Deprecated)']" /> <element name="TinymceDescription3" type ="button" selector="//span[text()='Description']" /> <element name="Tinymce3MSG" type="button" selector=".admin__field-error"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml index c4ce9d9c96d78..569b20a9c1479 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml @@ -17,7 +17,6 @@ <element name="multicheckDropdown" type="button" selector="div[data-role='grid-wrapper'] th.data-grid-multicheck-cell button.action-multicheck-toggle"/> <element name="multicheckOption" type="button" selector="//div[@data-role='grid-wrapper']//th[contains(@class, data-grid-multicheck-cell)]//li//span[text() = '{{label}}']" parameterized="true"/> <element name="bulkActionDropdown" type="button" selector="div.admin__data-grid-header-row.row div.action-select-wrap button.action-select"/> - <element name="bulkActionOption" type="button" selector="//div[contains(@class,'admin__data-grid-header-row') and contains(@class, 'row')]//div[contains(@class, 'action-select-wrap')]//ul/li/span[text() = '{{label}}']" parameterized="true"/> <element name="firstRow" type="button" selector="tr.data-row:nth-of-type(1)"/> <element name="productGridXRowYColumnButton" type="input" selector="table.data-grid tr.data-row:nth-child({{row}}) td:nth-child({{column}})" parameterized="true" timeout="30"/> <element name="productNameInNameColumn" type="input" selector="//td[4]/div[@class='data-grid-cell-content']"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AttributePropertiesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AttributePropertiesSection.xml index 55a1892389416..fbf07429b8408 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AttributePropertiesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AttributePropertiesSection.xml @@ -14,7 +14,7 @@ <element name="valueRequired" type="select" selector="#is_required"/> <element name="advancedProperties" type="button" selector="#advanced_fieldset-wrapper"/> <element name="defaultValue" type="input" selector="#default_value_text"/> - <element name="save" type="button" selector="#save"/> + <element name="save" type="button" selector="#save" timeout="30"/> <element name="saveAndEdit" type="button" selector="#save_and_edit_button"/> <element name="checkIfTabOpen" selector="//div[@id='advanced_fieldset-wrapper' and not(contains(@class,'opened'))]" type="button"/> </section> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml index f793cd14441f7..1f1a4ce9133e7 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCategoryMainSection"> <element name="modeListButton" type="button" selector="#mode-list" timeout="10"/> <element name="categoryTitle" type="text" selector="#page-title-heading span"/> @@ -21,5 +21,6 @@ <element name="productsList" type="text" selector="//ol[@class='products list items product-items']"/> <element name="categoryPageProductImagePlaceholderSmall" type="text" selector=".products-grid img[src*='placeholder/small_image.jpg']"/> <element name="categoryPageProductImage" type="text" selector=".products-grid img[src*='/{{var1}}']" parameterized="true"/> + <element name="categoryPageProductName" type="text" selector=".products.list.items.product-items li:nth-of-type({{line}}) .product-item-link" timeout="30" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml index 0621c18ef4ec3..f1456ac8ee387 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml @@ -11,5 +11,6 @@ <section name="StorefrontMessagesSection"> <element name="test" type="input" selector=".test"/> <element name="success" type="text" selector="div.message-success.success.message"/> + <element name="error" type="text" selector="div.message-error.error.message"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml index aa3e4abacdfcc..5742f44e1fedf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest.xml @@ -179,7 +179,7 @@ <expectedResult type="string">$1,500.00</expectedResult> <actualResult type="variable">grabTextFromSubtotalField4</actualResult> </assertEquals> - <grabTextFrom selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="grabTextFromCheckoutCartSummarySectionSubtotal1"/> + <grabTextFrom selector="{{StorefrontCheckoutCartSummarySection.subtotal}}" stepKey="grabTextFromCheckoutCartSummarySectionSubtotal1"/> <assertEquals message="Shopping cart summary section should contain subtotal $1,500" stepKey="assertSubtotalFieldFromCheckoutCartSummarySection1"> <expectedResult type="string">$1,500.00</expectedResult> <actualResult type="variable">grabTextFromCheckoutCartSummarySectionSubtotal1</actualResult> @@ -256,7 +256,7 @@ <expectedResult type="string">$4,000.00</expectedResult> <actualResult type="variable">grabTextFromSubtotalField7</actualResult> </assertEquals> - <grabTextFrom selector="{{CheckoutCartSummarySection.subtotal}}" stepKey="grabTextFromCheckoutCartSummarySectionSubtotal2"/> + <grabTextFrom selector="{{StorefrontCheckoutCartSummarySection.subtotal}}" stepKey="grabTextFromCheckoutCartSummarySectionSubtotal2"/> <assertEquals message="Shopping cart summary section should contain subtotal $4,000" stepKey="assertSubtotalFieldFromCheckoutCartSummarySection2"> <expectedResult type="string">$4,000.00</expectedResult> <actualResult type="variable">grabTextFromCheckoutCartSummarySectionSubtotal2</actualResult> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml new file mode 100644 index 0000000000000..2431d626387e3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml @@ -0,0 +1,168 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminFilteringCategoryProductsUsingScopeSelectorTest"> + <annotations> + <features value="Catalog"/> + <title value="Filtering Category Products using scope selector"/> + <description value="Filtering Category Products using scope selector"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-78408"/> + <group value="catalog"/> + </annotations> + <before> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--Create website, Sore adn Store View--> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="adminCreateWebsite"> + <argument name="newWebsiteName" value="secondWebsite"/> + <argument name="websiteCode" value="second_website"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="adminCreateStore"> + <argument name="website" value="secondWebsite"/> + <argument name="storeGroupName" value="Second Store"/> + <argument name="storeGroupCode" value="second_store"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="adminCreateStoreView"> + <argument name="storeGroup" value="secondStoreGroup"/> + <argument name="customStore" value="secondStore"/> + </actionGroup> + + <!--Create Simple Product and Category --> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct0"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="_defaultProduct" stepKey="createProduct1"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="_defaultProduct" stepKey="createProduct2"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="_defaultProduct" stepKey="createProduct12"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Set filter to product name and product0 not assigned to any website--> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="filterGroupedProductOptions"> + <argument name="product" value="_defaultProduct"/> + </actionGroup> + + <click selector="{{AdminProductGridSection.productGridNameProduct('$$createProduct0.name$$')}}" + stepKey="clickOpenProductForEdit"/> + <waitForPageLoad time="30" stepKey="waitForProductEditOpen"/> + + <scrollTo selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="scrollToWebsitesSection"/> + <click selector="{{ProductInWebsitesSection.sectionHeader}}" stepKey="clickToOpenWebsiteSection"/> + <waitForPageLoad stepKey="waitForToOpenedWebsiteSection"/> + <uncheckOption selector="{{ProductInWebsitesSection.website('Main Website')}}" stepKey="uncheckWebsite"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." + stepKey="seeSuccessMessage"/> + + <!-- Set filter to product name and product2 in website 2 only --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad time="30" stepKey="waitForProductsPageToLoad"/> + <click selector="{{AdminProductGridSection.productGridNameProduct('$$createProduct2.name$$')}}" + stepKey="clickOpenProductForEdit1"/> + <waitForPageLoad time="30" stepKey="waitForProductEditOpen1"/> + + <actionGroup ref="SelectProductInWebsitesActionGroup" stepKey="selectProductInWebsites"> + <argument name="website" value="secondWebsite"/> + </actionGroup> + <uncheckOption selector="{{ProductInWebsitesSection.website('Main Website')}}" stepKey="uncheckWebsite1"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct1"/> + <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." + stepKey="seeSuccessMessage1"/> + + <!-- Set filter to product name and product12 assigned to both websites 1 and 2 --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex1"/> + <waitForPageLoad time="30" stepKey="waitForProductsPageToLoad1"/> + <click selector="{{AdminProductGridSection.productGridNameProduct('$$createProduct12.name$$')}}" + stepKey="clickOpenProductForEdit2"/> + <waitForPageLoad time="30" stepKey="waitForProductEditOpen2"/> + + <actionGroup ref="SelectProductInWebsitesActionGroup" stepKey="selectProductInWebsites1"> + <argument name="website" value="secondWebsite"/> + </actionGroup> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct2"/> + <see selector="{{AdminProductMessagesSection.successMessage}}" userInput="You saved the product." + stepKey="seeSuccessMessage2"/> + </before> + <after> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="secondWebsite"/> + </actionGroup> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearProductFilters"/> + <deleteData createDataKey="createProduct0" stepKey="deleteProduct"/> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createProduct12" stepKey="deleteProduct3"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Step 1-2: Open Category page and Set scope selector to All Store Views--> + <amOnPage url="{{AdminCategoryPage.url}}" stepKey="goToCategoryPage"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad"/> + <click selector="{{AdminCategorySidebarTreeSection.categoryInTree($$createCategory.name$$)}}" + stepKey="clickCategoryName"/> + <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductSection"/> + <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct0.name$$)}}" + userInput="$$createProduct0.name$$" stepKey="seeProductName"/> + <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct1.name$$)}}" + userInput="$$createProduct1.name$$" stepKey="seeProductName1"/> + <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct2.name$$)}}" + userInput="$$createProduct2.name$$" stepKey="seeProductName2"/> + <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct12.name$$)}}" + userInput="$$createProduct12.name$$" stepKey="seeProductName3"/> + + <!-- Step 3: Set scope selector to Website1( Storeview for the Website 1) --> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminCategoryMainActionsSection.categoryStoreViewDropdownToggle}}" + stepKey="clickStoresList"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad1"/> + <click selector="{{AdminCategoryMainActionsSection.categoryStoreViewOption('Default Store View')}}" + stepKey="clickStoreView"/> + <waitForElementVisible selector="{{AdminCategoryMainActionsSection.categoryStoreViewModalAccept}}" + stepKey="waitForPopup1"/> + <click selector="{{AdminCategoryMainActionsSection.categoryStoreViewModalAccept}}" stepKey="clickActionAccept"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad2"/> + <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductSection1"/> + <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct1.name$$)}}" + userInput="$$createProduct1.name$$" stepKey="seeProductName4"/> + <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct12.name$$)}}" + userInput="$$createProduct12.name$$" stepKey="seeProductName5"/> + <dontSee selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct0.name$$)}}" + userInput="$$createProduct0.name$$" stepKey="dontSeeProductName"/> + <dontSee selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct2.name$$)}}" + userInput="$$createProduct2.name$$" stepKey="dontSeeProductName1"/> + + <!-- Step 4: Set scope selector to Website2 ( StopreView for Website 2) --> + <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> + <click selector="{{AdminCategoryMainActionsSection.categoryStoreViewDropdownToggle}}" + stepKey="clickStoresList1"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad3"/> + <click selector="{{AdminCategoryMainActionsSection.categoryStoreViewOption(secondStore.name)}}" + stepKey="clickStoreView1"/> + <waitForElementVisible selector="{{AdminCategoryMainActionsSection.categoryStoreViewModalAccept}}" + stepKey="waitForPopup2"/> + <click selector="{{AdminCategoryMainActionsSection.categoryStoreViewModalAccept}}" + stepKey="clickActionAccept1"/> + <waitForPageLoad stepKey="waitForCategoryPageLoad4"/> + <click selector="{{AdminCategoryProductsSection.sectionHeader}}" stepKey="openProductSection2"/> + <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct2.name$$)}}" + userInput="$$createProduct2.name$$" stepKey="seeProductName6"/> + <see selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct12.name$$)}}" + userInput="$$createProduct12.name$$" stepKey="seeProductName7"/> + <dontSee selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct0.name$$)}}" + userInput="$$createProduct0.name$$" stepKey="dontSeeProductName2"/> + <dontSee selector="{{AdminCategoryProductsGridSection.productGridNameProduct($$createProduct2.name$$)}}" + userInput="$$createProduct1.name$$" stepKey="dontSeeProductName3"/> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml new file mode 100644 index 0000000000000..197de39ead756 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml @@ -0,0 +1,146 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminRemoveImageAffectsAllScopesTest"> + <annotations> + <features value="Catalog"/> + <stories value="MAGETWO-73449: Changes in default scope not effect product images in other scopes"/> + <title value="Effect of product images changes in default scope to other scopes"/> + <description value="Product image should be deleted from all scopes"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-95344"/> + <group value="catalog"/> + </annotations> + <before> + <!-- login to admin, create default category and product --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + + <!-- Create first custom website, store, store view --> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createWebsite"> + <argument name="newWebsiteName" value="{{CustomWebSite.name}}"/> + <argument name="websiteCode" value="{{CustomWebSite.code}}"/> + </actionGroup> + + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createStore"> + <argument name="website" value="{{CustomWebSite.name}}"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> + </actionGroup> + + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createStoreView"> + <argument name="storeGroup" value="customStoreGroup"/> + <argument name="customStore" value="customStore"/> + </actionGroup> + + <!-- Create second custom website, store, store view --> + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createSecondWebsite"> + <argument name="newWebsiteName" value="{{SecondWebsite.name}}"/> + <argument name="websiteCode" value="{{SecondWebsite.code}}"/> + </actionGroup> + + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStore"> + <argument name="website" value="{{SecondWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> + </actionGroup> + + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="storeGroup" value="SecondStoreGroupUnique"/> + <argument name="customStore" value="SecondStoreUnique"/> + </actionGroup> + </before> + + <after> + <actionGroup ref="ResetWebUrlOptions" stepKey="resetUrlOption"/> + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteWebsite"> + <argument name="websiteName" value="{{CustomWebSite.name}}"/> + </actionGroup> + + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteSecondWebsite"> + <argument name="websiteName" value="{{SecondWebsite.name}}"/> + </actionGroup> + + <deleteData createDataKey="createCategory" stepKey="deletePreReqCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteFirstProduct"/> + + <!-- Open product index page, clear filters and change gridview to default view --> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductIndexPage"/> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearFilters"/> + <actionGroup ref="resetAdminDataGridToDefaultView" stepKey="resetAdminDataGridToDefaultView"/> + + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Open created product--> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchProduct"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProductPage"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!-- Add image to product --> + <actionGroup ref="addProductImage" stepKey="addFirstImageForProduct"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!-- Add second image to product --> + <actionGroup ref="addProductImage" stepKey="addSecondImageForProduct"> + <argument name="image" value="MagentoLogo"/> + </actionGroup> + + <!--"Product in Websites": select both Websites--> + <actionGroup ref="ProductSetWebsite" stepKey="productSetWebsite1"> + <argument name="website" value="CustomWebSite"/> + </actionGroup> + <actionGroup ref="ProductSetWebsite" stepKey="productSetWebsite2"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + + <!--Open created product--> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchProduct1"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProductPage1"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <!--Delete Image 1--> + <actionGroup ref="RemoveProductImage" stepKey="removeProductImage"/> + + <!--Click "Save" in the upper right corner--> + <actionGroup ref="saveProductForm" stepKey="saveProductFormAfterRemove"/> + + <!--Switch to "Store view 1"--> + <actionGroup ref="SwitchToTheNewStoreView" stepKey="selectStoreView"> + <argument name="storeViewName" value="customStore"/> + </actionGroup> + + <!-- Assert product first image not in admin product form --> + <actionGroup ref="AssertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInAdminProductPage"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + + <!--Switch to "Store view 2"--> + <actionGroup ref="SwitchToTheNewStoreView" stepKey="selectSecondStoreView"> + <argument name="storeViewName" value="SecondStoreUnique"/> + </actionGroup> + + <!-- Verify that Image 1 is deleted from the Second Store View list --> + <actionGroup ref="AssertProductImageNotInAdminProductPage" stepKey="assertProductImageNotInSecondStoreViewPage"> + <argument name="image" value="TestImageNew"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml new file mode 100644 index 0000000000000..4058112a2d76c --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml @@ -0,0 +1,341 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="CheckTierPricingOfProductsTest"> + <annotations> + <features value="Shopping Cart"/> + <stories value="MAGETWO-88254 - [Magento Cloud] 'Tier Pricing' of Products changes to 'Price' (without discount) after Updated Items and Quantities in the Order of B2B Store View."/> + <title value="Checking 'Tier Pricing' of Products and 'Price' (without discount) in the Order of B2B Store View"/> + <description value="Checking 'Tier Pricing' of Products and 'Price' (without discount) in the Order of B2B Store View"/> + <testCaseId value="MAGETWO-95117"/> + <severity value="CRITICAL"/> + <group value="tierPrice"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct1"> + <field key="price">123.00</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createProduct2"> + <field key="price">123.00</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createProduct3"> + <field key="price">123.00</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="SimpleProduct" stepKey="createProduct4"> + <field key="price">123.00</field> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + + <!--Login as admin--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <actionGroup ref="AdminCreateWebsiteActionGroup" stepKey="createSecondWebsite"> + <argument name="newWebsiteName" value="{{SecondWebsite.name}}"/> + <argument name="websiteCode" value="{{SecondWebsite.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStoreGroup"> + <argument name="website" value="{{SecondWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="storeGroup" value="SecondStoreGroupUnique"/> + <argument name="customStore" value="SecondStoreUnique"/> + </actionGroup> + + <!--Set Configuration--> + <createData entity="CatalogPriceScopeWebsite" stepKey="setPriceScopePerWebsite"/> + + <!--Set advanced pricing for all 4 products--> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct1"> + <argument name="product" value="$$createProduct1$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openProduct1ForEdit"> + <argument name="product" value="$$createProduct1$$"/> + </actionGroup> + <actionGroup ref="ProductSetWebsite" stepKey="setWebsiteForProduct1"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + <actionGroup ref="ProductSetAdvancedPricing" stepKey="setAdvancedPricingForProduct1"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct2"> + <argument name="product" value="$$createProduct2$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openProduct2ForEdit"> + <argument name="product" value="$$createProduct2$$"/> + </actionGroup> + <actionGroup ref="ProductSetWebsite" stepKey="setWebsiteForProduct2"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + <actionGroup ref="ProductSetAdvancedPricing" stepKey="setAdvancedPricingForProduct2"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct3"> + <argument name="product" value="$$createProduct3$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openProduct3ForEdit"> + <argument name="product" value="$$createProduct3$$"/> + </actionGroup> + <actionGroup ref="ProductSetWebsite" stepKey="setWebsiteForProduct3"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + <actionGroup ref="ProductSetAdvancedPricing" stepKey="setAdvancedPricingForProduct3"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForProduct4"> + <argument name="product" value="$$createProduct4$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openProduct4ForEdit"> + <argument name="product" value="$$createProduct4$$"/> + </actionGroup> + <actionGroup ref="ProductSetWebsite" stepKey="setWebsiteForProduct4"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + <actionGroup ref="ProductSetAdvancedPricing" stepKey="setAdvancedPricingForProduct4"> + <argument name="website" value="SecondWebsite"/> + </actionGroup> + + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductIndex"/> + <waitForPageLoad stepKey="waitForProductsIndexPageToLoad"/> + <actionGroup ref="resetAdminDataGridToDefaultView" stepKey="resetProductsGrid"/> + + <!--Edit customer info--> + <actionGroup ref="OpenEditCustomerFromAdminActionGroup" stepKey="openEditCustomerFrom"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="openCustomerAccountInformationSection"/> + <waitForPageLoad stepKey="waitCustomerAccountInformationSectionOpened"/> + <selectOption selector="{{AdminCustomerAccountInformationSection.group}}" userInput="Retailer" stepKey="setCustomerGroup"/> + <selectOption selector="{{AdminCustomerAccountInformationSection.storeView}}" userInput="{{SecondStoreUnique.name}}" stepKey="selectCustomerStore"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="saveCustomer"/> + <waitForPageLoad stepKey="waitForCustomerPageLoadAfterSave"/> + <see userInput="You saved the customer." stepKey="checkCustomerIsSaved"/> + + <amOnPage url="{{AdminCustomerPage.url}}" stepKey="navigateToCustomersIndex"/> + <waitForPageLoad stepKey="waitForCustomersIndexPageToLoad"/> + <actionGroup ref="resetAdminDataGridToDefaultView" stepKey="resetCustomersGrid"/> + + <!--Create Cart Price Rule--> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="openCartPriceRulesGrid"/> + <waitForPageLoad stepKey="waitForCartPriceRulesGrid"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <waitForPageLoad stepKey="waitForNewCartPriceRulePageIsLoaded"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="ship" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{SecondWebsite.name}}" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.customerGroups}}" userInput="Retailer" stepKey="selectCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="Specific Coupon" stepKey="selectCouponType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.couponCode}}" userInput="ship" stepKey="setCode"/> + <fillField selector="{{AdminCartPriceRulesFormSection.userPerCustomer}}" userInput="0" stepKey="setUserPerCustomer"/> + <fillField selector="{{AdminCartPriceRulesFormSection.userPerCoupon}}" userInput="0" stepKey="setUserPerCoupon"/> + <fillField selector="{{AdminCartPriceRulesFormSection.priority}}" userInput="0" stepKey="setPriority"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.freeShipping}}" userInput="For shipment with matching items" stepKey="selectFreeShippingType"/> + <click selector="{{AdminCartPriceRulesFormSection.saveAndContinue}}" stepKey="clickSaveAndContinueButton"/> + <waitForPageLoad stepKey="waitForCartPriceRuleSaved"/> + <see userInput="You saved the rule." stepKey="checkRuleSaved"/> + + <!--Create new order--> + <actionGroup ref="navigateToNewOrderPageExistingCustomer" stepKey="startToCreateNewOrder"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + <click selector="{{AdminOrderFormSelectWebsiteSection.website(SecondStoreUnique.name)}}" stepKey="selectStore"/> + <waitForPageLoad stepKey="waitForPageOpened"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickToAddProduct"/> + <waitForPageLoad stepKey="waitForProductsOpened"/> + <!--TEST CASE #1--> + <!--Add 3 products to order with specified quantity--> + <click selector="{{AdminOrderFormItemsSection.selectProduct($$createProduct1.name$$)}}" stepKey="selectProduct1"/> + <fillField selector="{{AdminOrderFormItemsSection.setQuantity($$createProduct1.name$$)}}" userInput="10" stepKey="addProductQuantity1"/> + + <click selector="{{AdminOrderFormItemsSection.selectProduct($$createProduct2.name$$)}}" stepKey="selectProduct2"/> + <fillField selector="{{AdminOrderFormItemsSection.setQuantity($$createProduct2.name$$)}}" userInput="10" stepKey="addProductQuantity2"/> + + <click selector="{{AdminOrderFormItemsSection.selectProduct($$createProduct3.name$$)}}" stepKey="selectProduct3"/> + <fillField selector="{{AdminOrderFormItemsSection.setQuantity($$createProduct3.name$$)}}" userInput="10" stepKey="addProductQuantity3"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="addProductsToOrder"/> + <waitForLoadingMaskToDisappear stepKey="wait"/> + <!--Verify tier price values--> + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct1.name$$)}}" stepKey="checkProductPrice1"/> + <assertEquals stepKey="verifyPrice1"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice1</actualResult> + </assertEquals> + + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct2.name$$)}}" stepKey="checkProductPrice2"/> + <assertEquals stepKey="verifyPrice2"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice2</actualResult> + </assertEquals> + + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct3.name$$)}}" stepKey="checkProductPrice3"/> + <assertEquals stepKey="verifyPrice3"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice3</actualResult> + </assertEquals> + + <!--Edit order and verify values--> + <waitForPageLoad stepKey="waitForPageLoaded2"/> + <click selector="{{AdminOrderFormItemsSection.customPrice($$createProduct1.name$$)}}" stepKey="clickOnCustomPrice"/> + <fillField selector="{{AdminOrderFormItemsSection.customQuantity($$createProduct1.name$$)}}" userInput="5" stepKey="clickOnQuantity"/> + <waitForLoadingMaskToDisappear stepKey="wait1"/> + <click selector="{{AdminOrderFormItemsSection.update}}" stepKey="clickToUpdate"/> + <waitForLoadingMaskToDisappear stepKey="wait2"/> + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct1.name$$)}}" stepKey="checkProductPrice4"/> + <assertEquals stepKey="verifyPrice4"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice2}}</expectedResult> + <actualResult type="variable">$checkProductPrice4</actualResult> + </assertEquals> + + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct2.name$$)}}" stepKey="checkProductPrice5"/> + <assertEquals stepKey="verifyPrice5"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice5</actualResult> + </assertEquals> + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct3.name$$)}}" stepKey="checkProductPrice6"/> + <assertEquals stepKey="verifyPrice6"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice3</actualResult> + </assertEquals> + + <!--Remove products from order--> + <selectOption selector="{{AdminOrderFormItemsSection.removeItems($$createProduct1.name$$)}}" userInput="Remove" stepKey="clickToRemove1"/> + <selectOption selector="{{AdminOrderFormItemsSection.removeItems($$createProduct2.name$$)}}" userInput="Remove" stepKey="clickToRemove2"/> + <selectOption selector="{{AdminOrderFormItemsSection.removeItems($$createProduct3.name$$)}}" userInput="Remove" stepKey="clickToRemove3"/> + <waitForLoadingMaskToDisappear stepKey="wait3"/> + <click selector="{{AdminOrderFormItemsSection.update}}" stepKey="clickToUpdate1"/> + <waitForPageLoad stepKey="waitProductsDeleted"/> + + <!--TEST CASE #2--> + <!--Add 3 products to order with specified quantity--> + <scrollToTopOfPage stepKey="scrollToTopOfPage"/> + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickToAddProduct1"/> + <click selector="{{AdminOrderFormItemsSection.selectProduct($$createProduct1.name$$)}}" stepKey="selectProduct5"/> + <fillField selector="{{AdminOrderFormItemsSection.setQuantity($$createProduct1.name$$)}}" userInput="10" stepKey="addProductQuantity5"/> + + <click selector="{{AdminOrderFormItemsSection.selectProduct($$createProduct2.name$$)}}" stepKey="selectProduct6"/> + <fillField selector="{{AdminOrderFormItemsSection.setQuantity($$createProduct2.name$$)}}" userInput="10" stepKey="addProductQuantity6"/> + + <click selector="{{AdminOrderFormItemsSection.selectProduct($$createProduct3.name$$)}}" stepKey="selectProduct7"/> + <fillField selector="{{AdminOrderFormItemsSection.setQuantity($$createProduct3.name$$)}}" userInput="10" stepKey="addProductQuantity7"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="addProductsToOrder1"/> + <waitForLoadingMaskToDisappear stepKey="waitProductsAddedToOrder"/> + <!--Verify tier price values--> + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct1.name$$)}}" stepKey="checkProductPrice7"/> + <assertEquals stepKey="verifyPrice7"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice7</actualResult> + </assertEquals> + + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct2.name$$)}}" stepKey="checkProductPrice8"/> + <assertEquals stepKey="verifyPrice8"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice8</actualResult> + </assertEquals> + + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct3.name$$)}}" stepKey="checkProductPrice9"/> + <assertEquals stepKey="verifyPrice9"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice9</actualResult> + </assertEquals> + + <!--Add one more product and verify values--> + <waitForPageLoad stepKey="waitForAllBlocksOnPageLoaded"/> + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickToAddcreateProduct2"/> + <waitForLoadingMaskToDisappear stepKey="wait8"/> + <click selector="{{AdminOrderFormItemsSection.selectProduct($$createProduct4.name$$)}}" stepKey="selectProduct8"/> + <fillField selector="{{AdminOrderFormItemsSection.setQuantity($$createProduct4.name$$)}}" userInput="10" stepKey="addProductQuantity9"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="addProductsToOrder2"/> + <waitForLoadingMaskToDisappear stepKey="wait9"/> + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct4.name$$)}}" stepKey="checkProductPrice10"/> + <assertEquals stepKey="verifyPrice10"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice10</actualResult> + </assertEquals> + + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct1.name$$)}}" stepKey="checkProductPrice11"/> + <assertEquals stepKey="verifyPrice11"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice11</actualResult> + </assertEquals> + + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct2.name$$)}}" stepKey="checkProductPrice12"/> + <assertEquals stepKey="verifyPrice12"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice12</actualResult> + </assertEquals> + + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct3.name$$)}}" stepKey="checkProductPrice13"/> + <assertEquals stepKey="verifyPrice13"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice13</actualResult> + </assertEquals> + + <selectOption selector="{{AdminOrderFormItemsSection.removeItems($$createProduct1.name$$)}}" userInput="Remove" stepKey="clickToRemove4"/> + <selectOption selector="{{AdminOrderFormItemsSection.removeItems($$createProduct2.name$$)}}" userInput="Remove" stepKey="clickToRemove5"/> + <selectOption selector="{{AdminOrderFormItemsSection.removeItems($$createProduct3.name$$)}}" userInput="Remove" stepKey="clickToRemove6"/> + <waitForLoadingMaskToDisappear stepKey="wait4"/> + <click selector="{{AdminOrderFormItemsSection.update}}" stepKey="clickToUpdate2"/> + <waitForLoadingMaskToDisappear stepKey="wait10"/> + + <!--TEST CASE #3--> + <waitForPageLoad stepKey="WaitProductsDeleted1"/> + <scrollToTopOfPage stepKey="scrollToTopOfPage1"/> + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickToAddProduct4" /> + <click selector="{{AdminOrderFormItemsSection.selectProduct($$createProduct1.name$$)}}" stepKey="selectProduct9"/> + <fillField selector="{{AdminOrderFormItemsSection.setQuantity($$createProduct1.name$$)}}" userInput="10" stepKey="addProductQuantity10"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="addProductsToOrder3"/> + <waitForLoadingMaskToDisappear stepKey="wait11"/> + <fillField selector="{{AdminOrderFormItemsSection.applyCoupon}}" userInput="ship" stepKey="addCouponCode"/> + <waitForLoadingMaskToDisappear stepKey="wait5"/> + <click selector="{{AdminOrderFormItemsSection.update}}" stepKey="clickToUpdate3"/> + <waitForLoadingMaskToDisappear stepKey="wait12"/> + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct1.name$$)}}" stepKey="checkProductPrice14"/> + <grabTextFrom selector="{{AdminOrderFormItemsSection.productPrice($$createProduct4.name$$)}}" stepKey="checkProductPrice15"/> + <assertEquals stepKey="verifyPrice14"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice14</actualResult> + </assertEquals> + <assertEquals stepKey="verifyPrice15"> + <expectedResult type="string">{{TestDataTierPrice.goldenPrice1}}</expectedResult> + <actualResult type="variable">$checkProductPrice15</actualResult> + </assertEquals> + + <after> + <deleteData createDataKey="createProduct1" stepKey="deleteProduct1"/> + <deleteData createDataKey="createProduct2" stepKey="deleteProduct2"/> + <deleteData createDataKey="createProduct3" stepKey="deleteProduct3"/> + <deleteData createDataKey="createProduct4" stepKey="deleteProduct4"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + + <createData entity="DefaultConfigCatalogPrice" stepKey="resetPriceScopeConfiguration"/> + + <actionGroup ref="AdminDeleteWebsiteActionGroup" stepKey="deleteSecondWebsite"> + <argument name="websiteName" value="{{SecondWebsite.name}}"/> + </actionGroup> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteCartPriceRule"> + <argument name="ruleName" value="ship"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logout"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml index 2599853f768bc..9575d7fea56fc 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/SaveProductWithCustomOptionsSecondWebsiteTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> <test name="SaveProductWithCustomOptionsAdditionalWebsiteTest"> <annotations> <features value="Save a product with Custom Options and assign to a different website"/> @@ -86,6 +86,7 @@ <seeElement selector="{{AdminProductMessagesSection.successMessage}}" stepKey="seeSaveProductMessageAgain"/> <click selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" stepKey="openCustomOptionsSection"/> + <scrollTo stepKey="scrollToCustomizableOptions" selector="{{AdminProductCustomizableOptionsSection.customizableOptions}}" /> <seeNumberOfElements selector=".admin__dynamic-rows[data-index='values'] tr.data-row" userInput="3" stepKey="see4RowsOfOptions"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml index e92c47d2a3f48..31ed251a34795 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml @@ -120,6 +120,7 @@ <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask3"/> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearGridFilter"/> <fillField selector="{{OrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="fillOrderNum"/> <click selector="{{OrdersGridSection.submitSearch}}" stepKey="submitSearch"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask4"/> @@ -176,4 +177,4 @@ <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> </test> -</tests> \ No newline at end of file +</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml index d5dd0d8a23806..b5f7ddd8f00cf 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml @@ -51,6 +51,7 @@ <conditionalClick selector="{{CheckoutPaymentSection.cartItemsArea}}" dependentSelector="{{CheckoutPaymentSection.cartItemsArea}}" visible="true" stepKey="exposeMiniCart"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskForCartItem"/> <waitForElement selector="{{CheckoutPaymentSection.cartItemsAreaActive}}" time="30" stepKey="waitForCartItemsAreaActive"/> + <waitForPageLoad stepKey="waitForCartToLoad"/> <see selector="{{CheckoutPaymentSection.cartItems}}" userInput="$$createProduct.name$$" stepKey="seeProductInCart"/> <conditionalClick selector="{{CheckoutPaymentSection.productOptionsByProductItemName($$createProduct.name$$)}}" dependentSelector="{{CheckoutPaymentSection.productOptionsActiveByProductItemName($$createProduct.name$$)}}" visible="false" stepKey="exposeProductOptions"/> <see selector="{{CheckoutPaymentSection.productOptionsActiveByProductItemName($$createProduct.name$$)}}" userInput="{{ProductOptionValueDropdownLongTitle1.title}}" stepKey="seeProductOptionValueDropdown1Input1"/> @@ -64,6 +65,7 @@ <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin1"/> <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearGridFilter"/> <fillField selector="{{AdminOrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="fillOrderNum"/> <click selector="{{AdminOrdersGridSection.submitSearch}}" stepKey="submitSearchOrderNum"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnSearch"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/composer.json b/app/code/Magento/Catalog/Test/Mftf/composer.json deleted file mode 100644 index 44c0032b3ee12..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/composer.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-indexer": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-msrp": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-catalog-rule": "100.0.0-dev", - "magento/functional-test-module-product-alert": "100.0.0-dev", - "magento/functional-test-module-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-catalog-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php index 9df0a6bc1eac0..b9e8abe18f4ac 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CollectionProviderTest.php @@ -57,10 +57,14 @@ public function testGetCollection() $linkedProductOneMock = $this->createMock(Product::class); $linkedProductTwoMock = $this->createMock(Product::class); $linkedProductThreeMock = $this->createMock(Product::class); + $linkedProductFourMock = $this->createMock(Product::class); + $linkedProductFiveMock = $this->createMock(Product::class); $linkedProductOneMock->expects($this->once())->method('getId')->willReturn(1); $linkedProductTwoMock->expects($this->once())->method('getId')->willReturn(2); $linkedProductThreeMock->expects($this->once())->method('getId')->willReturn(3); + $linkedProductFourMock->expects($this->once())->method('getId')->willReturn(4); + $linkedProductFiveMock->expects($this->once())->method('getId')->willReturn(5); $this->converterPoolMock->expects($this->once()) ->method('getConverter') @@ -71,9 +75,11 @@ public function testGetCollection() [$linkedProductOneMock, ['name' => 'Product One', 'position' => 10]], [$linkedProductTwoMock, ['name' => 'Product Two', 'position' => 2]], [$linkedProductThreeMock, ['name' => 'Product Three', 'position' => 2]], + [$linkedProductFourMock, ['name' => 'Product Four', 'position' => null]], + [$linkedProductFiveMock, ['name' => 'Product Five']], ]; - $this->converterMock->expects($this->exactly(3))->method('convert')->willReturnMap($map); + $this->converterMock->expects($this->exactly(5))->method('convert')->willReturnMap($map); $this->providerMock->expects($this->once()) ->method('getLinkedProducts') @@ -82,14 +88,18 @@ public function testGetCollection() [ $linkedProductOneMock, $linkedProductTwoMock, - $linkedProductThreeMock + $linkedProductThreeMock, + $linkedProductFourMock, + $linkedProductFiveMock, ] ); $expectedResult = [ - 2 => ['name' => 'Product Two', 'position' => 2], - 3 => ['name' => 'Product Three', 'position' => 2], - 10 => ['name' => 'Product One', 'position' => 10], + 0 => ['name' => 'Product Four', 'position' => 0], + 1 => ['name' => 'Product Five', 'position' => 0], + 2 => ['name' => 'Product Three', 'position' => 2], + 3 => ['name' => 'Product Two', 'position' => 2], + 4 => ['name' => 'Product One', 'position' => 10], ]; $actualResult = $this->model->getCollection($this->productMock, 'crosssell'); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php index bd8871c21de9e..4435446f77bfb 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php @@ -69,10 +69,17 @@ class ImageUploaderTest extends \PHPUnit\Framework\TestCase /** * Allowed extensions * - * @var string + * @var array */ private $allowedExtensions; + /** + * Allowed mime types + * + * @var array + */ + private $allowedMimeTypes; + protected function setUp() { $this->coreFileStorageDatabaseMock = $this->createMock( @@ -97,6 +104,7 @@ protected function setUp() $this->baseTmpPath = 'base/tmp/'; $this->basePath = 'base/real/'; $this->allowedExtensions = ['.jpg']; + $this->allowedMimeTypes = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png']; $this->imageUploader = new \Magento\Catalog\Model\ImageUploader( @@ -107,7 +115,8 @@ protected function setUp() $this->loggerMock, $this->baseTmpPath, $this->basePath, - $this->allowedExtensions + $this->allowedExtensions, + $this->allowedMimeTypes ); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Action/RowTest.php index 2572356cf855d..f095867ed5c39 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 @@ -8,7 +8,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) @@ -21,45 +23,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); @@ -70,11 +75,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(\Magento\Catalog\Model\Indexer\Product\Flat\FlatTableBuilder::class); @@ -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,19 +105,30 @@ 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, + 'resource' => $this->resource, + 'storeManager' => $this->storeManager, + 'productHelper' => $this->productIndexerHelper, + 'flatItemEraser' => $this->flatItemEraser, + 'flatItemWriter' => $this->flatItemWriter, 'flatTableBuilder' => $this->flatTableBuilder, ]); + + $objectManager->setBackwardCompatibleProperty($this->model, 'metadataPool', $metadataPool); } /** @@ -129,9 +143,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'); @@ -141,12 +155,11 @@ public function testExecuteWithNonExistingFlatTablesCreatesTables() public function testExecuteWithExistingFlatTablesCreatesTables() { $this->productIndexerHelper->expects($this->any())->method('getFlatTableName') - ->will($this->returnValue('store_flat_table')); + ->willReturn('store_flat_table'); $this->connection->expects($this->any())->method('isTableExists')->with('store_flat_table') - ->will($this->returnValue(true)); + ->willReturn(true); $this->flatItemEraser->expects($this->once())->method('removeDeletedProducts'); $this->flatTableBuilder->expects($this->never())->method('build')->with('store_id_1', ['product_id_1']); $this->model->execute('product_id_1'); } } - diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php index 627aa1848506e..f5ed96882b981 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php @@ -9,6 +9,7 @@ use Magento\Catalog\Model\View\Asset\PlaceholderFactory; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\Config\ScopeConfigInterface as ScopeConfig; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -81,6 +82,11 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ private $cacheManager; + /** + * @var ScopeConfig|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfig; + protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -89,7 +95,6 @@ protected function setUp() ->disableOriginalConstructor() ->getMockForAbstractClass(); $this->context->expects($this->any())->method('getCacheManager')->will($this->returnValue($this->cacheManager)); - $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManager::class) ->disableOriginalConstructor() ->setMethods(['getStore', 'getWebsite'])->getMock(); @@ -98,24 +103,20 @@ protected function setUp() $store->expects($this->any())->method('getId')->will($this->returnValue(1)); $store->expects($this->any())->method('getBaseUrl')->will($this->returnValue('http://magento.com/media/')); $this->storeManager->expects($this->any())->method('getStore')->will($this->returnValue($store)); - $this->config = $this->getMockBuilder(\Magento\Catalog\Model\Product\Media\Config::class) ->setMethods(['getBaseMediaPath'])->disableOriginalConstructor()->getMock(); $this->config->expects($this->any())->method('getBaseMediaPath')->will($this->returnValue('catalog/product')); $this->coreFileHelper = $this->getMockBuilder(\Magento\MediaStorage\Helper\File\Storage\Database::class) ->setMethods(['saveFile', 'deleteFolder'])->disableOriginalConstructor()->getMock(); - $this->mediaDirectory = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\Write::class) ->disableOriginalConstructor() ->setMethods(['create', 'isFile', 'isExist', 'getAbsolutePath']) ->getMock(); - $this->filesystem = $this->createMock(\Magento\Framework\Filesystem::class); $this->filesystem->expects($this->once())->method('getDirectoryWrite') ->with(DirectoryList::MEDIA) ->will($this->returnValue($this->mediaDirectory)); $this->factory = $this->createMock(\Magento\Framework\Image\Factory::class); - $this->viewAssetImageFactory = $this->getMockBuilder(ImageFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) @@ -141,6 +142,10 @@ function ($value) { return json_decode($value, true); } ); + $this->scopeConfig = $this->getMockBuilder(ScopeConfig::class) + ->setMethods(['getValue'])->getMockForAbstractClass(); + $this->scopeConfig->expects($this->any())->method('getValue') + ->with('system/upload_configuration/jpeg_quality')->willReturn(80); $this->image = $objectManager->getObject( \Magento\Catalog\Model\Product\Image::class, @@ -153,7 +158,8 @@ function ($value) { 'imageFactory' => $this->factory, 'viewAssetImageFactory' => $this->viewAssetImageFactory, 'viewAssetPlaceholderFactory' => $this->viewAssetPlaceholderFactory, - 'serializer' => $this->serializer + 'serializer' => $this->serializer, + 'scopeConfig' => $this->scopeConfig ] ); diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php index 9cca6955a986c..47ef3c999125f 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/GalleryTest.php @@ -5,35 +5,33 @@ */ namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product; -use PHPUnit_Framework_MockObject_MockObject as MockObject; - /** * Unit test for product media gallery resource. */ class GalleryTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\DB\Adapter\AdapterInterface|MockObject + * @var \Magento\Framework\DB\Adapter\AdapterInterface | \PHPUnit_Framework_MockObject_MockObject */ protected $connection; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\Gallery|MockObject + * @var \Magento\Catalog\Model\ResourceModel\Product\Gallery | \PHPUnit_Framework_MockObject_MockObject */ protected $resource; /** - * @var \Magento\Catalog\Model\Product|MockObject + * @var \Magento\Catalog\Model\Product | \PHPUnit_Framework_MockObject_MockObject */ protected $product; /** - * @var \Magento\Framework\DB\Select|MockObject + * @var \Magento\Framework\DB\Select | \PHPUnit_Framework_MockObject_MockObject */ protected $select; /** - * @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute|MockObject + * @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute | \PHPUnit_Framework_MockObject_MockObject */ protected $attribute; @@ -102,16 +100,16 @@ public function testLoadDataFromTableByValueId() $leftJoinTables = [ 0 => [ 0 => [ - 'store_value' => 'catalog_product_entity_media_gallery_value_video', - ], + 'store_value' => 'catalog_product_entity_media_gallery_value_video', + ], 1 => 'main.value_id = store_value.value_id AND store_value.store_id = 0', 2 => [ - 'video_provider' => 'provider', - 'video_url' => 'url', - 'video_title' => 'title', - 'video_description' => 'description', - 'video_metadata' => 'metadata', - ], + 'video_provider' => 'provider', + 'video_url' => 'url', + 'video_title' => 'title', + 'video_description' => 'description', + 'video_metadata' => 'metadata', + ], ], ]; $whereCondition = null; @@ -169,8 +167,8 @@ public function testLoadDataFromTableByValueId() ] ]; $this->connection->expects($this->once())->method('fetchAll') - ->with($this->select) - ->willReturn($resultRow); + ->with($this->select) + ->willReturn($resultRow); $methodResult = $this->resource->loadDataFromTableByValueId( $tableNameAlias, @@ -191,18 +189,18 @@ public function testLoadDataFromTableByValueIdNoColsWithWhere() $cols = null; $leftJoinTables = [ 0 => [ - 0 => [ - 'store_value' => 'catalog_product_entity_media_gallery_value_video', + 0 => [ + 'store_value' => 'catalog_product_entity_media_gallery_value_video', + ], + 1 => 'main.value_id = store_value.value_id AND store_value.store_id = 0', + 2 => [ + 'video_provider' => 'provider', + 'video_url' => 'url', + 'video_title' => 'title', + 'video_description' => 'description', + 'video_metadata' => 'metadata', + ], ], - 1 => 'main.value_id = store_value.value_id AND store_value.store_id = 0', - 2 => [ - 'video_provider' => 'provider', - 'video_url' => 'url', - 'video_title' => 'title', - 'video_description' => 'description', - 'video_metadata' => 'metadata', - ], - ], ]; $whereCondition = 'main.store_id = ' . $storeId; $getTableReturnValue = 'table'; @@ -261,8 +259,8 @@ public function testLoadDataFromTableByValueIdNoColsWithWhere() ]; $this->connection->expects($this->once())->method('fetchAll') - ->with($this->select) - ->willReturn($resultRow); + ->with($this->select) + ->willReturn($resultRow); $methodResult = $this->resource->loadDataFromTableByValueId( $tableNameAlias, @@ -313,118 +311,91 @@ public function testLoadGallery() ], ]; - $this->connection->method('getCheckSql') - ->with( - 'value.position IS NULL', - 'default_value.position', - 'value.position' - ) - ->willReturn($positionCheckSql); - $this->connection->method('select') - ->willReturn($this->select); - $this->select->method('from') - ->with( + $this->connection->expects($this->once())->method('getCheckSql')->with( + 'value.position IS NULL', + 'default_value.position', + 'value.position' + )->will($this->returnValue($positionCheckSql)); + $this->connection->expects($this->once())->method('select')->will($this->returnValue($this->select)); + $this->select->expects($this->at(0))->method('from')->with( + [ + 'main' => $getTableReturnValue, + ], + [ + 'value_id', + 'file' => 'value', + 'media_type' + ] + )->willReturnSelf(); + $this->select->expects($this->at(1))->method('joinInner')->with( + ['entity' => $getTableReturnValue], + 'main.value_id = entity.value_id', + ['entity_id'] + )->willReturnSelf(); + $this->product->expects($this->at(0))->method('getData') + ->with('entity_id')->willReturn($productId); + $this->product->expects($this->at(1))->method('getStoreId')->will($this->returnValue($storeId)); + $this->connection->expects($this->exactly(2))->method('quoteInto')->withConsecutive( + ['value.store_id = ?'], + ['default_value.store_id = ?'] + )->willReturnOnConsecutiveCalls( + 'value.store_id = ' . $storeId, + 'default_value.store_id = ' . 0 + ); + $this->connection->expects($this->any())->method('getIfNullSql')->will( + $this->returnValueMap([ [ - 'main' => $getTableReturnValue, + '`value`.`label`', + '`default_value`.`label`', + 'IFNULL(`value`.`label`, `default_value`.`label`)' ], [ - 'value_id', - 'file' => 'value', - 'media_type' - ] - ) - ->willReturnSelf(); - $this->select->method('joinInner') - ->with( - ['entity' => $getTableReturnValue], - 'main.value_id = entity.value_id', - ['entity_id'] - ) - ->willReturnSelf(); - $this->product->method('getData') - ->with('entity_id') - ->willReturn($productId); - $this->product->method('getStoreId') - ->willReturn($storeId); - $this->connection->method('quoteInto') - ->withConsecutive( - ['value.store_id = ?'], - ['default_value.store_id = ?'] - ) - ->willReturnOnConsecutiveCalls( - 'value.store_id = ' . $storeId, - 'default_value.store_id = ' . 0 - ); - $this->connection->method('getIfNullSql') - ->willReturnMap( - [ - [ - '`value`.`label`', - '`default_value`.`label`', - 'IFNULL(`value`.`label`, `default_value`.`label`)' - ], - [ - '`value`.`position`', - '`default_value`.`position`', - 'IFNULL(`value`.`position`, `default_value`.`position`)' - ], - [ - '`value`.`disabled`', - '`default_value`.`disabled`', - 'IFNULL(`value`.`disabled`, `default_value`.`disabled`)' - ] - ] - ); - $this->select->method('joinLeft') - ->withConsecutive( - [ - ['value' => $getTableReturnValue], - $quoteInfoReturnValue, - [] + '`value`.`position`', + '`default_value`.`position`', + 'IFNULL(`value`.`position`, `default_value`.`position`)' ], [ - ['default_value' => $getTableReturnValue], - $quoteDefaultInfoReturnValue, - [] + '`value`.`disabled`', + '`default_value`.`disabled`', + 'IFNULL(`value`.`disabled`, `default_value`.`disabled`)' ] - ) - ->willReturnSelf(); - $this->select->method('columns') - ->with([ - 'label' => 'IFNULL(`value`.`label`, `default_value`.`label`)', - 'position' => 'IFNULL(`value`.`position`, `default_value`.`position`)', - 'disabled' => 'IFNULL(`value`.`disabled`, `default_value`.`disabled`)', - 'label_default' => 'default_value.label', - 'position_default' => 'default_value.position', - 'disabled_default' => 'default_value.disabled' ]) - ->willReturnSelf(); - $this->select->method('where') - ->withConsecutive( - [ - 'main.attribute_id = ?', - $attributeId - ], - [ - 'main.disabled = 0' - ], - [ - 'value.store_id = ' . $storeId . ' OR default_value.store_id = 0' - ], - [ - 'entity.entity_id = ?', - $productId - ] - ) - ->willReturnSelf(); - $this->select->method('order') - ->with($positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC) - ->willReturnSelf(); - $this->connection->method('fetchAll') - ->with($this->select) - ->willReturn($resultRow); + ); + $this->select->expects($this->at(2))->method('joinLeft')->with( + ['value' => $getTableReturnValue], + $quoteInfoReturnValue, + [] + )->willReturnSelf(); + $this->select->expects($this->at(3))->method('joinLeft')->with( + ['default_value' => $getTableReturnValue], + $quoteDefaultInfoReturnValue, + [] + )->willReturnSelf(); + $this->select->expects($this->at(4))->method('columns')->with([ + 'label' => 'IFNULL(`value`.`label`, `default_value`.`label`)', + 'position' => 'IFNULL(`value`.`position`, `default_value`.`position`)', + 'disabled' => 'IFNULL(`value`.`disabled`, `default_value`.`disabled`)', + 'label_default' => 'default_value.label', + 'position_default' => 'default_value.position', + 'disabled_default' => 'default_value.disabled' + ])->willReturnSelf(); + $this->select->expects($this->at(5))->method('where')->with( + 'main.attribute_id = ?', + $attributeId + )->willReturnSelf(); + $this->select->expects($this->at(6))->method('where') + ->with('main.disabled = 0')->willReturnSelf(); + $this->select->expects($this->at(8))->method('where') + ->with('entity.entity_id = ?', $productId) + ->willReturnSelf(); + $this->select->expects($this->once())->method('order') + ->with($positionCheckSql . ' ' . \Magento\Framework\DB\Select::SQL_ASC) + ->willReturnSelf(); + $this->connection->expects($this->once())->method('fetchAll') + ->with($this->select) + ->willReturn($resultRow); - self::assertEquals($resultRow, $this->resource->loadProductGalleryByAttributeId($this->product, $attributeId)); + $this->assertEquals($resultRow, $this->resource->loadProductGalleryByAttributeId($this->product, $attributeId)); } public function testInsertGalleryValueInStore() diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php index ed737df708ab8..2dad7e8495b11 100644 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Categories.php @@ -289,6 +289,7 @@ protected function customizeCategoriesField(array $meta) 'source' => 'product_details', 'displayArea' => 'insideGroup', 'sortOrder' => 20, + 'dataScope' => $fieldCode, ], ], ] diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php index c56d3d2d7d354..6a6d74c6cb9b3 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php @@ -32,6 +32,7 @@ use Magento\Ui\DataProvider\Mapper\FormElement as FormElementMapper; use Magento\Ui\DataProvider\Mapper\MetaProperties as MetaPropertiesMapper; use Magento\Eav\Model\ResourceModel\Entity\Attribute\CollectionFactory as AttributeCollectionFactory; +use \Magento\Catalog\Model\Product\Type as ProductType; /** * Class Eav @@ -398,7 +399,7 @@ public function modifyData(array $data) foreach ($attributes as $attribute) { if (null !== ($attributeValue = $this->setupAttributeData($attribute))) { - if ($attribute->getFrontendInput() === 'price' && is_scalar($attributeValue)) { + if ($this->isPriceAttribute($attribute, $attributeValue)) { $attributeValue = $this->formatPrice($attributeValue); } $data[$productId][self::DATA_SOURCE_DEFAULT][$attribute->getAttributeCode()] = $attributeValue; @@ -409,6 +410,32 @@ public function modifyData(array $data) return $data; } + /** + * Obtain if given attribute is a price + * + * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute + * @param string|integer $attributeValue + * @return bool + */ + private function isPriceAttribute(ProductAttributeInterface $attribute, $attributeValue) + { + return $attribute->getFrontendInput() === 'price' + && is_scalar($attributeValue) + && !$this->isBundleSpecialPrice($attribute); + } + + /** + * Obtain if current product is bundle and given attribute is special_price + * + * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute + * @return bool + */ + private function isBundleSpecialPrice(ProductAttributeInterface $attribute) + { + return $this->locator->getProduct()->getTypeId() === ProductType::TYPE_BUNDLE + && $attribute->getAttributeCode() === ProductAttributeInterface::CODE_SPECIAL_PRICE; + } + /** * Resolve data persistence * diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php index ec3ef58ded569..e598773ac368d 100755 --- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php +++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php @@ -373,7 +373,8 @@ protected function customizeNameListeners(array $meta) $skuPath . static::META_CONFIG_PATH, $meta, [ - 'autoImportIfEmpty' => true + 'autoImportIfEmpty' => true, + 'validation' => ['no-marginal-whitespace' => true] ] ); diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json index 57bb7d1e4ccef..4535e527d2dec 100644 --- a/app/code/Magento/Catalog/composer.json +++ b/app/code/Magento/Catalog/composer.json @@ -34,7 +34,7 @@ "magento/module-catalog-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "102.0.5", + "version": "102.0.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Catalog/etc/adminhtml/di.xml b/app/code/Magento/Catalog/etc/adminhtml/di.xml index 9739ee28a6dae..dd3008a4a98a4 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/di.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/di.xml @@ -193,4 +193,16 @@ <type name="Magento\Eav\Api\AttributeSetRepositoryInterface"> <plugin name="remove_products" type="Magento\Catalog\Plugin\Model\AttributeSetRepository\RemoveProducts"/> </type> + <type name="Magento\Catalog\Block\Adminhtml\Product\Edit\Action\Attribute\Tab\Attributes"> + <arguments> + <argument name="excludeFields" xsi:type="array"> + <item name="0" xsi:type="string">category_ids</item> + <item name="1" xsi:type="string">gallery</item> + <item name="2" xsi:type="string">image</item> + <item name="3" xsi:type="string">media_gallery</item> + <item name="4" xsi:type="string">quantity_and_stock_status</item> + <item name="5" xsi:type="string">tier_price</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml index 1f78de93b369d..77b69185fc355 100644 --- a/app/code/Magento/Catalog/etc/adminhtml/system.xml +++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml @@ -185,5 +185,19 @@ </field> </group> </section> + <section id="system" translate="label" type="text" sortOrder="900" showInDefault="1" showInWebsite="1" showInStore="1"> + <class>separator-top</class> + <label>System</label> + <tab>advanced</tab> + <resource>Magento_Config::config_system</resource> + <group id="upload_configuration" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="1" showInStore="1"> + <label>Images Upload Configuration</label> + <field id="jpeg_quality" translate="label comment" type="text" sortOrder="100" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <label>Quality</label> + <validate>validate-digits validate-digits-range digits-range-1-100 required-entry</validate> + <comment>Jpeg quality for resized images 1-100%.</comment> + </field> + </group> + </section> </system> </config> diff --git a/app/code/Magento/Catalog/etc/config.xml b/app/code/Magento/Catalog/etc/config.xml index 3569c0a27b83f..b7733a1ab0239 100644 --- a/app/code/Magento/Catalog/etc/config.xml +++ b/app/code/Magento/Catalog/etc/config.xml @@ -52,6 +52,11 @@ <forbidden_extensions>php,exe</forbidden_extensions> </custom_options> </catalog> + <indexer> + <catalog_product_price> + <dimensions_mode>none</dimensions_mode> + </catalog_product_price> + </indexer> <system> <media_storage_configuration> <allowed_resources> @@ -60,6 +65,9 @@ <product_custom_options_fodler>custom_options</product_custom_options_fodler> </allowed_resources> </media_storage_configuration> + <upload_configuration> + <jpeg_quality>80</jpeg_quality> + </upload_configuration> </system> <design> <watermark> diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index eec0b8597f6e6..22c6495c2bc93 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -212,6 +212,12 @@ <item name="gif" xsi:type="string">gif</item> <item name="png" xsi:type="string">png</item> </argument> + <argument name="allowedMimeTypes" xsi:type="array"> + <item name="jpg" xsi:type="string">image/jpg</item> + <item name="jpeg" xsi:type="string">image/jpeg</item> + <item name="gif" xsi:type="string">image/gif</item> + <item name="png" xsi:type="string">image/png</item> + </argument> </arguments> </virtualType> <type name="Magento\Catalog\Controller\Adminhtml\Category\Image\Upload"> @@ -550,7 +556,6 @@ <argument name="commands" xsi:type="array"> <item name="imagesResizeCommand" xsi:type="object">Magento\Catalog\Console\Command\ImagesResizeCommand</item> <item name="productAttributesCleanUp" xsi:type="object">Magento\Catalog\Console\Command\ProductAttributesCleanUp</item> - <item name="setPriceDimensionsMode" xsi:type="object">Magento\Catalog\Console\Command\PriceIndexerDimensionsModeSetCommand</item> </argument> </arguments> </type> @@ -1130,4 +1135,18 @@ <argument name="productType" xsi:type="string">virtual</argument> </arguments> </virtualType> + <type name="Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand"> + <arguments> + <argument name="dimensionSwitchers" xsi:type="array"> + <item name="catalog_product_price" xsi:type="object">Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcher</item> + </argument> + </arguments> + </type> + <type name="Magento\Indexer\Console\Command\IndexerShowDimensionsModeCommand"> + <arguments> + <argument name="indexers" xsi:type="array"> + <item name="catalog_product_price" xsi:type="string">catalog_product_price</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml index 8a5f1919f78be..3cbfa0f29d74f 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/js.phtml @@ -55,7 +55,7 @@ function bindAttributeInputType() { checkOptionsPanelVisibility(); switchDefaultValueField(); - if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect' || $('frontend_input').value=='price')){ + if($('frontend_input') && ($('frontend_input').value=='boolean' || $('frontend_input').value=='select' || $('frontend_input').value=='multiselect' || $('frontend_input').value=='price')){ if($('is_filterable') && !$('is_filterable').getAttribute('readonly')){ $('is_filterable').disabled = false; } 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 @@ <% } %> <ul data-mage-init='{"menu":[]}'> <% _.each(data.items, function(value) { %> - <li <%- data.optionData(value) %>><a href="#"><%- value.label %></a></li> + <li <%= data.optionData(value) %>><a href="#"><%- value.label %></a></li> <% }); %> </ul> <% if (!data.term && data.items.length && !data.allShown()) { %> diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/wysiwyg/js.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/wysiwyg/js.phtml index 9c568cab16d84..ed380722d8b43 100644 --- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/wysiwyg/js.phtml +++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/wysiwyg/js.phtml @@ -43,6 +43,7 @@ var catalogWysiwygEditor = { if (this.modal) { this.modal.html(jQuery(data).html()); + this.modal.modal('option', 'firedElementId', elementId); } else { this.modal = jQuery(data).modal({ title: '<?= /* @escapeNotVerified */ __('WYSIWYG Editor') ?>', diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js index 72cad03eabe1b..1d64418e36ff5 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/category-checkbox-tree.js @@ -230,6 +230,7 @@ define([ categoryLoader.on('beforeload', function (treeLoader, node) { treeLoader.baseParams.id = node.attributes.id; + treeLoader.baseParams.selected = options.jsFormObject.updateElement.value; }); /* eslint-disable */ diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml index b1af46b80552d..038bea86e7d4e 100644 --- a/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml +++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml @@ -21,7 +21,7 @@ $label = $block->getChildData($alias, 'title'); ?> <div class="data item title" - aria-labeledby="tab-label-<?= /* @escapeNotVerified */ $alias ?>-title" + aria-labelledby="tab-label-<?= /* @escapeNotVerified */ $alias ?>-title" data-role="collapsible" id="tab-label-<?= /* @escapeNotVerified */ $alias ?>"> <a class="data switch" tabindex="-1" diff --git a/app/code/Magento/CatalogAnalytics/Test/Mftf/composer.json b/app/code/Magento/CatalogAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 5b0388559f9b5..0000000000000 --- a/app/code/Magento/CatalogAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json index 9a36d7a98c7b2..ead59ef212600 100644 --- a/app/code/Magento/CatalogAnalytics/composer.json +++ b/app/code/Magento/CatalogAnalytics/composer.json @@ -7,7 +7,7 @@ "magento/module-catalog": "102.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index 6a14eb8b7a817..bf46c0efb2a74 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -352,7 +352,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity * @param \Magento\Framework\App\ResourceConnection $resource * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Psr\Log\LoggerInterface $logger - * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection + * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $collectionFactory * @param \Magento\ImportExport\Model\Export\ConfigInterface $exportConfig * @param \Magento\Catalog\Model\ResourceModel\ProductFactory $productFactory * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory $attrSetColFactory @@ -361,9 +361,10 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity * @param \Magento\Catalog\Model\ResourceModel\Product\Option\CollectionFactory $optionColFactory * @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeColFactory * @param Product\Type\Factory $_typeFactory - * @param \Magento\Catalog\Model\Product\LinkTypeProvider $linkTypeProvider - * @param \Magento\CatalogImportExport\Model\Export\RowCustomizerInterface $rowCustomizer + * @param ProductEntity\LinkTypeProvider $linkTypeProvider + * @param RowCustomizerInterface $rowCustomizer * @param array $dateAttrCodes + * @throws \Magento\Framework\Exception\LocalizedException * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -517,10 +518,13 @@ protected function getMediaGallery(array $productIds) if (empty($productIds)) { return []; } + + $productEntityJoinField = $this->getProductEntityLinkField(); + $select = $this->_connection->select()->from( ['mgvte' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery_value_to_entity')], [ - "mgvte.{$this->getProductEntityLinkField()}", + "mgvte.$productEntityJoinField", 'mgvte.value_id' ] )->joinLeft( @@ -532,7 +536,7 @@ protected function getMediaGallery(array $productIds) ] )->joinLeft( ['mgv' => $this->_resourceModel->getTableName('catalog_product_entity_media_gallery_value')], - '(mg.value_id = mgv.value_id)', + "(mg.value_id = mgv.value_id) and (mgvte.$productEntityJoinField = mgv.$productEntityJoinField)", [ 'mgv.label', 'mgv.position', @@ -540,14 +544,14 @@ protected function getMediaGallery(array $productIds) 'mgv.store_id' ] )->where( - "mgvte.{$this->getProductEntityLinkField()} IN (?)", + "mgvte.$productEntityJoinField IN (?)", $productIds ); $rowMediaGallery = []; $stmt = $this->_connection->query($select); while ($mediaRow = $stmt->fetch()) { - $rowMediaGallery[$mediaRow[$this->getProductEntityLinkField()]][] = [ + $rowMediaGallery[$mediaRow[$productEntityJoinField]][] = [ '_media_attribute_id' => $mediaRow['attribute_id'], '_media_image' => $mediaRow['filename'], '_media_label' => $mediaRow['label'], @@ -689,6 +693,8 @@ protected function updateDataWithCategoryColumns(&$dataRow, &$rowCategories, $pr } /** + * Get header columns + * * {@inheritdoc} */ public function _getHeaderColumns() @@ -748,6 +754,8 @@ protected function _getExportMainAttrCodes() } /** + * Get entity collection + * * {@inheritdoc} */ protected function _getEntityCollection($resetCollection = false) @@ -793,9 +801,8 @@ protected function getItemsPerPage() // Maximal Products limit $maxProductsLimit = 5000; - $this->_itemsPerPage = intval( - ($memoryLimit * $memoryUsagePercent - memory_get_usage(true)) / $memoryPerProduct - ); + $this->_itemsPerPage = (int) + ($memoryLimit * $memoryUsagePercent - memory_get_usage(true)) / $memoryPerProduct; if ($this->_itemsPerPage < $minProductsLimit) { $this->_itemsPerPage = $minProductsLimit; } @@ -819,9 +826,8 @@ protected function paginateCollection($page, $pageSize) } /** - * Export process - * * @return string + * @throws \Magento\Framework\Exception\LocalizedException */ public function export() { @@ -855,7 +861,11 @@ public function export() } /** - * {@inheritdoc} + * Apply filter to collection and add not skipped attributes to select. + * + * @param \Magento\Eav\Model\Entity\Collection\AbstractCollection $collection + * @return \Magento\Eav\Model\Entity\Collection\AbstractCollection + * * @since 100.2.0 */ protected function _prepareEntityCollection(\Magento\Eav\Model\Entity\Collection\AbstractCollection $collection) @@ -917,11 +927,10 @@ protected function getExportData() } /** - * Load products' data from the collection - * and filter it (if needed). + * Load products' data from the collection and filter it (if needed). * - * @return array Keys are product IDs, values arrays with keys as store IDs - * and values as store-specific versions of Product entity. + * @return array Keys are product IDs, values arrays with keys as store ID + * and values as store-specific versions of Product entity. */ protected function loadCollection(): array { diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index 743bcddede9c3..3cf167e9e37e7 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -21,6 +21,8 @@ use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; use Magento\Store\Model\Store; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; /** * Import entity product model @@ -708,6 +710,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ private $mediaProcessor; + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + /** * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param \Magento\ImportExport\Helper\Data $importExportData @@ -748,6 +755,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity * @param array $dateAttrCodes * @param CatalogConfig $catalogConfig * @param MediaGalleryProcessor $mediaProcessor + * @param ProductRepositoryInterface|null $productRepository * @throws \Magento\Framework\Exception\LocalizedException * * @SuppressWarnings(PHPMD.ExcessiveParameterList) @@ -792,7 +800,8 @@ public function __construct( array $data = [], array $dateAttrCodes = [], CatalogConfig $catalogConfig = null, - MediaGalleryProcessor $mediaProcessor = null + MediaGalleryProcessor $mediaProcessor = null, + ProductRepositoryInterface $productRepository = null ) { $this->_eventManager = $eventManager; $this->stockRegistry = $stockRegistry; @@ -847,6 +856,8 @@ public function __construct( ->_initTypeModels() ->_initSkus(); $this->validator->init($this); + $this->productRepository = $productRepository ?? \Magento\Framework\App\ObjectManager::getInstance() + ->get(ProductRepositoryInterface::class); } /** @@ -1543,6 +1554,7 @@ protected function _saveProducts() $tierPrices = []; $mediaGallery = []; $labelsForUpdate = []; + $imagesForChangeVisibility = []; $uploadedImages = []; $previousType = null; $prevAttributeSet = null; @@ -1561,7 +1573,16 @@ protected function _saveProducts() } $rowScope = $this->getRowScope($rowData); - $rowData[self::URL_KEY] = $this->getUrlKey($rowData); + $urlKey = $this->getUrlKey($rowData); + if (!empty($rowData[self::URL_KEY])) { + // If url_key column and its value were in the CSV file + $rowData[self::URL_KEY] = $urlKey; + } else if ($this->isNeedToChangeUrlKey($rowData)) { + // If url_key column was empty or even not declared in the CSV file but by the rules it is need to + // be setteed. In case when url_key is generating from name column we have to ensure that the bunch + // of products will pass for the event with url_key column. + $bunch[$rowNum][self::URL_KEY] = $rowData[self::URL_KEY] = $urlKey; + } $rowSku = $rowData[self::COL_SKU]; @@ -1631,6 +1652,14 @@ protected function _saveProducts() $websiteId = $this->storeResolver->getWebsiteCodeToId($websiteCode); $this->websitesCache[$rowSku][$websiteId] = true; } + } else { + $product = $this->retrieveProductBySku($rowSku); + if ($product) { + $websiteIds = $product->getWebsiteIds(); + foreach ($websiteIds as $websiteId) { + $this->websitesCache[$rowSku][$websiteId] = true; + } + } } // 3. Categories phase @@ -1662,21 +1691,24 @@ protected function _saveProducts() } // 5. Media gallery phase - $disabledImages = []; list($rowImages, $rowLabels) = $this->getImagesFromRow($rowData); $storeId = !empty($rowData[self::COL_STORE]) ? $this->getStoreIdByCode($rowData[self::COL_STORE]) : Store::DEFAULT_STORE_ID; - if (isset($rowData['_media_is_disabled']) && strlen(trim($rowData['_media_is_disabled']))) { - $disabledImages = array_flip( - explode($this->getMultipleValueSeparator(), $rowData['_media_is_disabled']) - ); + $imageHiddenStates = $this->getImagesHiddenStates($rowData); + foreach (array_keys($imageHiddenStates) as $image) { + if (array_key_exists($rowSku, $existingImages) + && array_key_exists($image, $existingImages[$rowSku]) + ) { + $rowImages[self::COL_MEDIA_IMAGE][] = $image; + $uploadedImages[$image] = $image; + } + if (empty($rowImages)) { - foreach (array_keys($disabledImages) as $disabledImage) { - $rowImages[self::COL_MEDIA_IMAGE][] = $disabledImage; - } + $rowImages[self::COL_MEDIA_IMAGE][] = $image; } } + $rowData[self::COL_MEDIA_IMAGE] = []; /* @@ -1710,12 +1742,22 @@ protected function _saveProducts() if ($uploadedFile && !isset($mediaGallery[$storeId][$rowSku][$uploadedFile])) { if (isset($existingImages[$rowSku][$uploadedFile])) { + $currentFileData = $existingImages[$rowSku][$uploadedFile]; if (isset($rowLabels[$column][$columnImageKey]) - && $rowLabels[$column][$columnImageKey] != $existingImages[$rowSku][$uploadedFile]['label'] + && $rowLabels[$column][$columnImageKey] != $currentFileData['label'] ) { $labelsForUpdate[] = [ 'label' => $rowLabels[$column][$columnImageKey], - 'imageData' => $existingImages[$rowSku][$uploadedFile] + 'imageData' => $currentFileData, + ]; + } + + if (array_key_exists($uploadedFile, $imageHiddenStates) + && $currentFileData['disabled'] != $imageHiddenStates[$uploadedFile] + ) { + $imagesForChangeVisibility[] = [ + 'disabled' => $imageHiddenStates[$uploadedFile], + 'imageData' => $currentFileData, ]; } } else { @@ -1724,9 +1766,11 @@ protected function _saveProducts() } $mediaGallery[$storeId][$rowSku][$uploadedFile] = [ 'attribute_id' => $this->getMediaGalleryAttributeId(), - 'label' => isset($rowLabels[$column][$columnImageKey]) ? $rowLabels[$column][$columnImageKey] : '', + 'label' => isset($rowLabels[$column][$columnImageKey]) + ? $rowLabels[$column][$columnImageKey] : '', 'position' => ++$position, - 'disabled' => isset($disabledImages[$columnImage]) ? '1' : '0', + 'disabled' => isset($imageHiddenStates[$columnImage]) + ? $imageHiddenStates[$columnImage] : '0', 'value' => $uploadedFile, ]; } @@ -1847,6 +1891,8 @@ protected function _saveProducts() $mediaGallery )->_saveProductAttributes( $attributes + )->updateMediaGalleryVisibility( + $imagesForChangeVisibility )->updateMediaGalleryLabels( $labelsForUpdate ); @@ -1861,6 +1907,34 @@ protected function _saveProducts() } /** + * Prepare array with image states (visible or hidden from product page) + * + * @param array $rowData + * @return array + */ + private function getImagesHiddenStates(array $rowData): array + { + $statesArray = []; + $mappingArray = [ + '_media_is_disabled' => '1', + ]; + + foreach ($mappingArray as $key => $value) { + if (isset($rowData[$key]) && strlen(trim($rowData[$key]))) { + $items = explode($this->getMultipleValueSeparator(), $rowData[$key]); + + foreach ($items as $item) { + $statesArray[$item] = $value; + } + } + } + + return $statesArray; + } + + /** + * Resolve valid category ids from provided row data. + * * @param array $rowData * @return array */ @@ -1883,7 +1957,13 @@ protected function processRowCategories($rowData) . ' ' . $error['exception']->getMessage() ); } + } else { + $product = $this->retrieveProductBySku($rowData['sku']); + if ($product) { + $categoryIds = $product->getCategoryIds(); + } } + return $categoryIds; } @@ -2205,7 +2285,7 @@ public function getEntityTypeCode() * Returns array of new products data with SKU as key. All SKU keys are in lowercase for avoiding creation of * new products with the same SKU in different letter cases. * - * @var string $sku + * @param string $sku * @return array */ public function getNewSku($sku = null) @@ -2401,6 +2481,8 @@ public function validateRow(array $rowData, $rowNum) } /** + * Check if need to validate url key. + * * @param array $rowData * @return bool */ @@ -2408,8 +2490,8 @@ private function isNeedToValidateUrlKey($rowData) { return (!empty($rowData[self::URL_KEY]) || !empty($rowData[self::COL_NAME])) && (empty($rowData[self::COL_VISIBILITY]) - || $rowData[self::COL_VISIBILITY] - !== (string)Visibility::getOptionArray()[Visibility::VISIBILITY_NOT_VISIBLE]); + || $rowData[self::COL_VISIBILITY] + !== (string)Visibility::getOptionArray()[Visibility::VISIBILITY_NOT_VISIBLE]); } /** @@ -2709,14 +2791,17 @@ protected function getProductUrlSuffix($storeId = null) } /** + * Retrieve url key from provided row data. + * * @param array $rowData * @return string + * * @since 100.0.3 */ protected function getUrlKey($rowData) { if (!empty($rowData[self::URL_KEY])) { - return strtolower($rowData[self::URL_KEY]); + return $this->productUrl->formatUrlKey($rowData[self::URL_KEY]); } if (!empty($rowData[self::COL_NAME])) { @@ -2738,6 +2823,26 @@ protected function getResource() return $this->_resource; } + /** + * Whether a url key is needed to be change. + * + * @param array $rowData + * @return bool + */ + private function isNeedToChangeUrlKey(array $rowData): bool + { + $urlKey = $this->getUrlKey($rowData); + $productExists = $this->isSkuExist($rowData[self::COL_SKU]); + $markedToEraseUrlKey = isset($rowData[self::URL_KEY]); + // The product isn't new and the url key index wasn't marked for change. + if (!$urlKey && $productExists && !$markedToEraseUrlKey) { + // Seems there is no need to change the url key + return false; + } + + return true; + } + /** * Get product entity link field * @@ -2782,6 +2887,21 @@ private function updateMediaGalleryLabels(array $labels) $this->mediaProcessor->updateMediaGalleryLabels($labels); } + /** + * Update 'disabled' field for media gallery entity + * + * @param array $images + * @return $this + */ + private function updateMediaGalleryVisibility(array $images): Product + { + if (!empty($images)) { + $this->mediaProcessor->updateMediaGalleryVisibility($images); + } + + return $this; + } + /** * Parse values from multiple attributes fields * @@ -2864,4 +2984,21 @@ private function formatStockDataForRow(array $rowData) return $row; } + + /** + * Retrieve product by sku. + * + * @param string $sku + * @return \Magento\Catalog\Api\Data\ProductInterface|null + */ + private function retrieveProductBySku(string $sku) + { + try { + $product = $this->productRepository->get($sku); + } catch (NoSuchEntityException $e) { + return null; + } + + return $product; + } } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php index d2d8770f5bc83..f9872c0b2acd6 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php @@ -66,6 +66,8 @@ public function __construct( } /** + * Initialize categories to be processed + * * @return $this */ protected function initCategories() @@ -112,6 +114,9 @@ protected function createCategory($name, $parentId) if (!($parentCategory = $this->getCategoryById($parentId))) { $parentCategory = $this->categoryFactory->create()->load($parentId); } + + // Set StoreId to 0 to generate URL Keys global and prevent generating url rewrites just for default website + $category->setStoreId(0); $category->setPath($parentCategory->getPath()); $category->setParentId($parentId); $category->setName($name); diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php index ec7c6a1172996..fee677105dcc1 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/MediaGalleryProcessor.php @@ -103,7 +103,7 @@ public function __construct( /** * Save product media gallery. * - * @param $mediaGalleryData + * @param array $mediaGalleryData * @return void */ public function saveMediaGallery(array $mediaGalleryData) @@ -152,14 +152,37 @@ public function saveMediaGallery(array $mediaGalleryData) * @return void */ public function updateMediaGalleryLabels(array $labels) + { + $this->updateMediaGalleryField($labels, 'label'); + } + + /** + * Update 'disabled' field for media gallery entity + * + * @param array $images + * @return void + */ + public function updateMediaGalleryVisibility(array $images) + { + $this->updateMediaGalleryField($images, 'disabled'); + } + + /** + * Update value for requested field in media gallery entities + * + * @param array $data + * @param string $field + * @return void + */ + private function updateMediaGalleryField(array $data, string $field) { $insertData = []; - foreach ($labels as $label) { - $imageData = $label['imageData']; + foreach ($data as $datum) { + $imageData = $datum['imageData']; - if ($imageData['label'] === null) { + if ($imageData[$field] === null) { $insertData[] = [ - 'label' => $label['label'], + $field => $datum[$field], $this->getProductEntityLinkField() => $imageData[$this->getProductEntityLinkField()], 'value_id' => $imageData['value_id'], 'store_id' => Store::DEFAULT_STORE_ID, @@ -168,7 +191,7 @@ public function updateMediaGalleryLabels(array $labels) $this->connection->update( $this->mediaGalleryValueTableName, [ - 'label' => $label['label'], + $field => $datum[$field], ], [ $this->getProductEntityLinkField() . ' = ?' => $imageData[$this->getProductEntityLinkField()], @@ -224,6 +247,7 @@ public function getExistingImages(array $bunch) ), [ 'label' => 'mgv.label', + 'disabled' => 'mgv.disabled', ] )->joinInner( ['pe' => $this->productEntityTableName], @@ -263,7 +287,7 @@ private function initMediaGalleryResources() /** * Save media gallery data per store. * - * @param $storeId + * @param int $storeId * @param array $mediaGalleryData * @param array $newMediaValues * @param array $valueToProductId diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php index d2b03936f4f0f..cc4e780942a08 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Option.php @@ -336,6 +336,11 @@ class Option extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity */ private $optionTypeTitles; + /** + * @var array + */ + private $lastOptionTitle; + /** * @param \Magento\ImportExport\Model\ResourceModel\Import\Data $importData * @param ResourceConnection $resource @@ -1119,6 +1124,8 @@ protected function _getMultiRowFormat($rowData) } /** + * Process option row. + * * @param string $name * @param array $optionRow * @return array @@ -1185,6 +1192,7 @@ private function addFileOptions($result, $optionRow) /** * Import data rows. + * * Additional store view data (option titles) will be sought in store view specified import file rows * * @return boolean @@ -1212,7 +1220,6 @@ protected function _importData() $parentCount = []; $childCount = []; $optionsToRemove = []; - foreach ($bunch as $rowNumber => $rowData) { if (isset($optionId, $valueId) && empty($rowData[Product::COL_STORE_VIEW_CODE])) { $nextOptionId = $optionId; @@ -1257,9 +1264,17 @@ protected function _importData() $childCount ); $this->_collectOptionTitle($combinedData, $prevOptionId, $titles); + $this->checkOptionTitles( + $options, + $titles, + $combinedData, + $prevOptionId, + $optionId, + $products, + $prices + ); } } - // Remove all existing options if import behaviour is APPEND // in other case remove options for products with empty "custom_options" row only if ($this->getBehavior() != \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND) { @@ -1268,7 +1283,6 @@ protected function _importData() // Remove options for products with empty "custom_options" row $this->_deleteEntities($optionsToRemove); } - // Save prepared custom options data if ($this->_isReadyForSaving($options, $titles, $typeValues)) { $types = [ @@ -1276,6 +1290,7 @@ protected function _importData() 'prices' => $typePrices, 'titles' => $typeTitles ]; + $this->setLastOptionTitle($titles); $this->savePreparedCustomOptions($products, $options, $titles, $prices, $types); } } @@ -1283,6 +1298,65 @@ protected function _importData() return true; } + /** + * Check options titles. + * + * If products were split up between bunches, + * this function will add needed option for option titles. + * + * @param array $options + * @param array $titles + * @param array $combinedData + * @param int $prevOptionId + * @param int $optionId + * @param array $products + * @param array $prices + * @return void + */ + private function checkOptionTitles( + array &$options, + array &$titles, + array $combinedData, + int &$prevOptionId, + int &$optionId, + array $products, + array $prices + ) { + $titlesCount = count($titles); + if ($titlesCount > 0 && count($options) !== $titlesCount) { + $combinedData[Product::COL_STORE_VIEW_CODE] = ''; + $optionId--; + $option = $this->_collectOptionMainData( + $combinedData, + $prevOptionId, + $optionId, + $products, + $prices + ); + if ($option) { + $options[] = $option; + } + } + } + + /** + * Setting last Custom Option Title + * to use it later in _collectOptionTitle + * to set correct title for default store view. + * + * @param array $titles + * @return void + */ + private function setLastOptionTitle(array &$titles) + { + if (count($titles) > 0) { + end($titles); + $key = key($titles); + $this->lastOptionTitle[$key] = $titles[$key]; + } + } + + /** * Load data of existed products * @@ -1413,8 +1487,12 @@ protected function _collectOptionTitle(array $rowData, $prevOptionId, array &$ti $defaultStoreId = Store::DEFAULT_STORE_ID; if (!empty($rowData[self::COLUMN_TITLE])) { if (!isset($titles[$prevOptionId][$defaultStoreId])) { - // ensure default title is set - $titles[$prevOptionId][$defaultStoreId] = $rowData[self::COLUMN_TITLE]; + if (isset($this->lastOptionTitle[$prevOptionId])) { + $titles[$prevOptionId] = $this->lastOptionTitle[$prevOptionId]; + unset($this->lastOptionTitle); + } else { + $titles[$prevOptionId][$defaultStoreId] = $rowData[self::COLUMN_TITLE]; + } } $titles[$prevOptionId][$this->_rowStoreId] = $rowData[self::COLUMN_TITLE]; } diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php index 60bfdd56a718e..793ad935363d3 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php @@ -7,6 +7,7 @@ use Magento\CatalogImportExport\Model\Import\Product; use Magento\Framework\Validator\AbstractValidator; +use Magento\Catalog\Model\Product\Attribute\Backend\Sku; /** * Class Validator @@ -69,6 +70,8 @@ protected function textValidation($attrCode, $type) $val = $this->string->cleanString($this->_rowData[$attrCode]); if ($type == 'text') { $valid = $this->string->strlen($val) < Product::DB_MAX_TEXT_LENGTH; + } else if ($attrCode == Product::COL_SKU) { + $valid = $this->string->strlen($val) <= SKU::SKU_MAX_LENGTH; } else { $valid = $this->string->strlen($val) < Product::DB_MAX_VARCHAR_LENGTH; } diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/composer.json b/app/code/Magento/CatalogImportExport/Test/Mftf/composer.json deleted file mode 100644 index fd870de402ec9..0000000000000 --- a/app/code/Magento/CatalogImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogImportExport/composer.json b/app/code/Magento/CatalogImportExport/composer.json index d217a665cf924..39b05acc4e3b6 100644 --- a/app/code/Magento/CatalogImportExport/composer.json +++ b/app/code/Magento/CatalogImportExport/composer.json @@ -16,7 +16,7 @@ "ext-ctype": "*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php b/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php index c9b6abeb72e23..2d011e24f4105 100644 --- a/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php +++ b/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php @@ -72,7 +72,7 @@ public function getProductStockStatusBySku($productSku, $scopeId = null); * @param float $qty * @param int $currentPage * @param int $pageSize - * @return \Magento\CatalogInventory\Api\Data\StockStatusCollectionInterface + * @return \Magento\CatalogInventory\Api\Data\StockItemCollectionInterface */ public function getLowStockItems($scopeId, $qty, $currentPage = 1, $pageSize = 0); diff --git a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/Option.php b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/Option.php index 5b3a441333432..c114ed2758657 100644 --- a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/Option.php +++ b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator/Initializer/Option.php @@ -120,7 +120,7 @@ public function initialize( /** * if option's qty was updates we also need to update quote item qty */ - $quoteItem->setData('qty', intval($qty)); + $quoteItem->setData('qty', (int)$qty); } if ($result->getMessage() !== null) { $option->setMessage($result->getMessage()); diff --git a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php index 4cffc678314b2..9dc24b78fd7c0 100644 --- a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php +++ b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php @@ -11,9 +11,9 @@ use Magento\Catalog\Model\ProductFactory; use Magento\CatalogInventory\Api\Data\StockItemInterface; use Magento\CatalogInventory\Model\Spi\StockStateProviderInterface; +use Magento\Framework\DataObject\Factory as ObjectFactory; use Magento\Framework\Locale\FormatInterface; use Magento\Framework\Math\Division as MathDivision; -use Magento\Framework\DataObject\Factory as ObjectFactory; /** * Interface StockStateProvider @@ -115,13 +115,13 @@ public function checkQuoteItemQty(StockItemInterface $stockItem, $qty, $summaryQ $result->setItemIsQtyDecimal($stockItem->getIsQtyDecimal()); if (!$stockItem->getIsQtyDecimal()) { $result->setHasQtyOptionUpdate(true); - $qty = intval($qty); + $qty = (int)$qty; /** * Adding stock data to quote item */ $result->setItemQty($qty); $qty = $this->getNumber($qty); - $origQty = intval($origQty); + $origQty = (int)$origQty; $result->setOrigQty($origQty); } diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/composer.json b/app/code/Magento/CatalogInventory/Test/Mftf/composer.json deleted file mode 100644 index 979e05c4b59fe..0000000000000 --- a/app/code/Magento/CatalogInventory/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-inventory", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json index 2b823cdb9cc08..41fd9db15f15a 100644 --- a/app/code/Magento/CatalogInventory/composer.json +++ b/app/code/Magento/CatalogInventory/composer.json @@ -14,7 +14,7 @@ "magento/module-sales": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml index 14ce874baecc4..409038a3f541a 100644 --- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml +++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml @@ -285,7 +285,7 @@ <settings> <scopeLabel>[GLOBAL]</scopeLabel> <validation> - <rule name="validate-number" xsi:type="boolean">true</rule> + <rule name="validate-greater-than-zero" xsi:type="boolean">true</rule> </validation> <label translate="true">Maximum Qty Allowed in Shopping Cart</label> <dataScope>max_sale_qty</dataScope> diff --git a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php index d049d74bd2601..3d1ac9744ef8b 100644 --- a/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php +++ b/app/code/Magento/CatalogRule/Controller/Adminhtml/Promo/Widget/CategoriesJson.php @@ -77,10 +77,11 @@ public function execute() if (!($category = $this->_initCategory())) { return; } + $selected = $this->getRequest()->getPost('selected', ''); $block = $this->_view->getLayout()->createBlock( \Magento\Catalog\Block\Adminhtml\Category\Checkboxes\Tree::class )->setCategoryIds( - [$categoryId] + explode(',', $selected) ); $this->getResponse()->representJson( $block->getTreeJson($category) diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml new file mode 100644 index 0000000000000..72e082392059e --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- action group to create a new catalog price rule giving a catalogRule entity --> + <actionGroup name="CreateCatalogPriceRule"> + <arguments> + <argument name="catalogRule" defaultValue="CustomCatalogRule"/> + </arguments> + <!-- Go to the admin Catalog rule grid and add a new one --> + <amOnPage url="{{AdminCatalogPriceRuleGridPage.url}}" stepKey="goToAdminCatalogPriceRuleGridPage"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <click selector="{{AdminMainActionsSection.add}}" stepKey="addNewRule"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> + + <!-- Fill the form according the attributes of the entity --> + <fillField selector="{{AdminCatalogPriceRuleSection.ruleName}}" userInput="{{catalogRule.name}}" stepKey="fillName"/> + <fillField selector="{{AdminCatalogPriceRuleSection.description}}" userInput="{{catalogRule.description}}" stepKey="fillDescription"/> + <selectOption selector="{{AdminCatalogPriceRuleSection.websites}}" parameterArray="{{catalogRule.websites}}" stepKey="selectWebsites"/> + <selectOption selector="{{AdminCatalogPriceRuleSection.customerGroups}}" parameterArray="{{catalogRule.groups}}" stepKey="selectCustomerGroups"/> + + <click selector="{{AdminCatalogPriceRuleSection.actionsTab}}" stepKey="openActionDropdown"/> + <selectOption selector="{{AdminCatalogPriceRuleActionsSection.apply}}" userInput="{{catalogRule.simple_action}}" stepKey="discountType"/> + <fillField selector="{{AdminCatalogPriceRuleActionsSection.discountAmount}}" userInput="{{catalogRule.discount_amount}}" stepKey="fillDiscountValue"/> + <selectOption selector="{{AdminCatalogPriceRuleActionsSection.disregardRules}}" userInput="Yes" stepKey="discardSubsequentRules"/> + + <!-- Scroll to top and either save or save and apply after the action group --> + <scrollToTopOfPage stepKey="scrollToTop"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSave"/> + <waitForPageLoad stepKey="waitForRuleSaved"/> + <see userInput="You saved the rule." stepKey="verifyRuleSaved"/> + </actionGroup> + + <actionGroup name="RemoveCatalogPriceRule"> + <arguments> + <argument name="ruleName" defaultValue="CustomCatalogRule.name"/> + </arguments> + <amOnPage url="{{AdminCatalogPriceRuleGridPage.url}}" stepKey="goToAdminCatalogPriceRuleGridPage"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + + <fillField selector="{{AdminCatalogPriceRuleGridSection.filterByRuleName}}" userInput="{{ruleName}}" stepKey="filterByRuleName"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearch"/> + <click selector="{{AdminGridTableSection.row('1')}}" stepKey="clickEdit"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> + + <click selector="{{AdminMainActionsSection.delete}}" stepKey="clickToDelete"/> + <waitForElementVisible selector="{{AdminConfirmationModalSection.ok}}" stepKey="waitForElementVisible"/> + <click selector="{{AdminConfirmationModalSection.ok}}" stepKey="clickToConfirm"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear2"/> + <see userInput="You deleted the rule." stepKey="verifyRuleIsDeleted"/> + </actionGroup> + <!--Add Catalog Rule Condition With product SKU--> + <actionGroup name="newCatalogPriceRuleByUIWithConditionIsSKU" extends="CreateCatalogPriceRule"> + <arguments> + <argument name="productSku" type="string"/> + </arguments> + <click selector="{{AdminCatalogPriceRuleSection.conditionsTab}}" after="discardSubsequentRules" stepKey="openConditionsTab"/> + <click selector="{{AdminCatalogPriceRuleConditionsSection.newCondition}}" after="openConditionsTab" stepKey="addNewCondition"/> + <selectOption selector="{{AdminCatalogPriceRuleConditionsSection.conditionSelect('1')}}" userInput="Magento\CatalogRule\Model\Rule\Condition\Product|sku" after="addNewCondition" stepKey="selectTypeCondition"/> + <click selector="{{AdminCatalogPriceRuleConditionsSection.targetEllipsis('1')}}" after="selectTypeCondition" stepKey="clickEllipsis"/> + <fillField selector="{{AdminCatalogPriceRuleConditionsSection.targetInput('1', '1')}}" userInput="{{productSku}}" after="clickEllipsis" stepKey="fillProductSku"/> + <click selector="{{AdminCatalogPriceRuleConditionsSection.applyButton('1', '1')}}" after="fillProductSku" stepKey="clickApply"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml new file mode 100644 index 0000000000000..534a2c8cc6de2 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="CustomCatalogRule" type="catalogRule"> + <data key="name" unique="suffix">CatalogPriceRule</data> + <data key="description">Catalog Price Rule Description</data> + <data key="is_active">1</data> + <array key="websites"> + <item>Main Website</item> + </array> + <array key="groups"> + <item>General</item> + </array> + <data key="simple_action">by_percent</data> + <data key="discount_amount">10</data> + </entity> + <entity name="CatalogRule96PercentDiscount" type="catalogRule"> + <data key="name" unique="suffix">CatalogPriceRule</data> + <data key="description">Catalog Price Rule Description</data> + <data key="is_active">1</data> + <array key="groups"> + <item>NOT LOGGED IN</item> + </array> + <array key="websites"> + <item>Main Website</item> + </array> + <data key="simple_action">by_percent</data> + <data key="discount_amount">96</data> + </entity> +</entities> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleEditPage.xml b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleEditPage.xml new file mode 100644 index 0000000000000..eda619d8bd898 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleEditPage.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="AdminCatalogPriceRuleEditPage" url="catalog_rule/promo_catalog/edit/id/{{ruleId}}/" module="Magento_CatalogRule" area="admin" parameterized="true"> + <section name="AdminMainActionsSection"/> + <section name="AdminCatalogPriceRuleSection"/> + <section name="AdminCatalogPriceRuleActionsSection"/> + </page> +</pages> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleGridPage.xml b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleGridPage.xml new file mode 100644 index 0000000000000..063f68f9b5f86 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Page/AdminCatalogPriceRuleGridPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="AdminCatalogPriceRuleGridPage" url="catalog_rule/promo_catalog/" module="Magento_CatalogRule" area="admin"> + <section name="AdminCatalogPriceRuleGridSection"/> + </page> +</pages> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleActionsSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleActionsSection.xml new file mode 100644 index 0000000000000..38e5b8a0c2206 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleActionsSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminCatalogPriceRuleActionsSection"> + <element name="apply" type="select" selector="[name='simple_action']"/> + <element name="discountAmount" type="input" selector="[name='discount_amount']"/> + <element name="disregardRules" type="select" selector="[name='stop_rules_processing']"/> + </section> +</sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleConditionsSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleConditionsSection.xml new file mode 100644 index 0000000000000..f522fdaa03f89 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleConditionsSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogPriceRuleConditionsSection"> + <element name="newCondition" type="button" selector=".rule-param.rule-param-new-child" timeout="30"/> + <element name="conditionSelect" type="select" selector="select#conditions__{{var}}__new_child" parameterized="true" timeout="30"/> + <element name="targetEllipsis" type="button" selector="//li[{{var}}]//a[@class='label'][text() = '...']" parameterized="true" timeout="30"/> + <element name="targetInput" type="input" selector="input#conditions__{{var1}}--{{var2}}__value" parameterized="true"/> + <element name="applyButton" type="button" selector="#conditions__{{var1}}__children li:nth-of-type({{var2}}) a.rule-param-apply" parameterized="true" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleGridSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleGridSection.xml new file mode 100644 index 0000000000000..5224147c51804 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleGridSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogPriceRuleGridSection"> + <element name="filterByRuleName" type="input" selector="#promo_catalog_grid_filter_name"/> + <element name="attribute" type="text" selector="//td[contains(text(), '{{arg}}')]" parameterized="true"/> + <element name="applyRulesButton" type="button" selector="#apply_rules" timeout="30"/> + </section> +</sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleSection.xml new file mode 100644 index 0000000000000..bbf5ed6053cd2 --- /dev/null +++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminCatalogPriceRuleSection.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCatalogPriceRuleSection"> + <element name="saveAndApply" type="button" selector="#save_and_apply" timeout="30"/> + <element name="saveAndContinue" type="button" selector="#save_and_continue" timeout="30"/> + + <element name="ruleName" type="input" selector="[name='name']"/> + <element name="description" type="textarea" selector="[name='description']"/> + <element name="status" type="select" selector="[name='is_active']"/> + + <element name="websites" type="select" selector="[name='website_ids']"/> + <element name="websitesOptions" type="select" selector="[name='website_ids'] option"/> + <element name="customerGroups" type="select" selector="[name='customer_group_ids']"/> + <element name="customerGroupsOptions" type="select" selector="[name='customer_group_ids'] option"/> + + <element name="fromDateButton" type="button" selector="[name='from_date'] + button" timeout="15"/> + <element name="toDateButton" type="button" selector="[name='to_date'] + button" timeout="15"/> + <element name="todayDate" type="button" selector="#ui-datepicker-div [data-handler='today']"/> + <element name="priority" type="input" selector="[name='sort_order']"/> + <element name="conditionsTab" type="block" selector="[data-index='block_promo_catalog_edit_tab_conditions']"/> + <element name="actionsTab" type="block" selector="[data-index='actions']"/> + </section> +</sections> diff --git a/app/code/Magento/CatalogRule/Test/Mftf/composer.json b/app/code/Magento/CatalogRule/Test/Mftf/composer.json deleted file mode 100644 index f32e010b042b0..0000000000000 --- a/app/code/Magento/CatalogRule/Test/Mftf/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-rule", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-rule": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-catalog-rule-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json index 595cd121a1a1c..4067d7044b158 100644 --- a/app/code/Magento/CatalogRule/composer.json +++ b/app/code/Magento/CatalogRule/composer.json @@ -17,7 +17,7 @@ "magento/module-catalog-rule-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "101.0.4", + "version": "101.0.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/composer.json b/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/composer.json deleted file mode 100644 index 94fe8339dc0e3..0000000000000 --- a/app/code/Magento/CatalogRuleConfigurable/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-rule-configurable", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-rule": "100.0.0-dev", - "magento/magento-composer-installer": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-catalog-rule": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json index ecad2425eccc8..93a937ffcc45e 100644 --- a/app/code/Magento/CatalogRuleConfigurable/composer.json +++ b/app/code/Magento/CatalogRuleConfigurable/composer.json @@ -13,7 +13,7 @@ "magento/module-catalog-rule": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php index 381fb573b21a1..286b6427c8132 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php @@ -316,11 +316,20 @@ public function getSearchableAttributes($backendType = null) /** @var \Magento\Eav\Model\Entity\Attribute[] $attributes */ $attributes = $productAttributes->getItems(); + /** + * @deprecated Event argument catelogsearch_searchable_attributes_load_after. + * @see catalogsearch_searchable_attributes_load_after instead. + */ $this->eventManager->dispatch( 'catelogsearch_searchable_attributes_load_after', ['engine' => $this->engine, 'attributes' => $attributes] ); + $this->eventManager->dispatch( + 'catalogsearch_searchable_attributes_load_after', + ['engine' => $this->engine, 'attributes' => $attributes] + ); + $entity = $this->eavConfig->getEntityType(\Magento\Catalog\Model\Product::ENTITY)->getEntity(); foreach ($attributes as $attribute) { diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/composer.json b/app/code/Magento/CatalogSearch/Test/Mftf/composer.json deleted file mode 100644 index 5fe659b81c4b7..0000000000000 --- a/app/code/Magento/CatalogSearch/Test/Mftf/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-search", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-indexer": "100.0.0-dev", - "magento/functional-test-module-search": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogSearch/composer.json b/app/code/Magento/CatalogSearch/composer.json index 2903ee3789da7..2a72af9cf96a5 100644 --- a/app/code/Magento/CatalogSearch/composer.json +++ b/app/code/Magento/CatalogSearch/composer.json @@ -20,7 +20,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CatalogUrlRewrite/Model/WebapiProductUrlPathGenerator.php b/app/code/Magento/CatalogUrlRewrite/Model/WebapiProductUrlPathGenerator.php new file mode 100644 index 0000000000000..487943c2305b4 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Model/WebapiProductUrlPathGenerator.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Model; + +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; + +/** + * Class for creating product url through web-api. + */ +class WebapiProductUrlPathGenerator extends ProductUrlPathGenerator +{ + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig + * @param \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator + * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository + * @param CollectionFactory $collectionFactory + */ + public function __construct( + \Magento\Store\Model\StoreManagerInterface $storeManager, + \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, + \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator $categoryUrlPathGenerator, + \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, + CollectionFactory $collectionFactory + ) { + parent::__construct($storeManager, $scopeConfig, $categoryUrlPathGenerator, $productRepository); + $this->collectionFactory = $collectionFactory; + } + + /** + * @inheritdoc + */ + protected function prepareProductUrlKey(\Magento\Catalog\Model\Product $product) + { + $urlKey = $product->getUrlKey(); + if ($urlKey === '' || $urlKey === null) { + $urlKey = $this->prepareUrlKey($product->formatUrlKey($product->getName())); + } + + return $product->formatUrlKey($urlKey); + } + + /** + * Crete url key if it does not exist yet. + * + * @param string $urlKey + * @return string + */ + private function prepareUrlKey(string $urlKey) : string + { + /** @var ProductCollection $collection */ + $collection = $this->collectionFactory->create(); + $collection->addFieldToFilter('url_key', ['like' => $urlKey]); + if ($collection->getSize() !== 0) { + $urlKey = $urlKey . '-1'; + $urlKey = $this->prepareUrlKey($urlKey); + } + + return $urlKey; + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php index c4d67f447e2cf..6eda8dd0b61ee 100644 --- a/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php +++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProductProcessUrlRewriteSavingObserver.php @@ -6,11 +6,15 @@ namespace Magento\CatalogUrlRewrite\Observer; use Magento\Catalog\Model\Product; +use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator; use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator; +use Magento\Framework\App\ObjectManager; use Magento\UrlRewrite\Model\UrlPersistInterface; -use Magento\UrlRewrite\Service\V1\Data\UrlRewrite; use Magento\Framework\Event\ObserverInterface; +/** + * Class ProductProcessUrlRewriteSavingObserver + */ class ProductProcessUrlRewriteSavingObserver implements ObserverInterface { /** @@ -23,22 +27,33 @@ class ProductProcessUrlRewriteSavingObserver implements ObserverInterface */ private $urlPersist; + /** + * @var ProductUrlPathGenerator + */ + private $productUrlPathGenerator; + /** * @param ProductUrlRewriteGenerator $productUrlRewriteGenerator * @param UrlPersistInterface $urlPersist + * @param ProductUrlPathGenerator|null $productUrlPathGenerator */ public function __construct( ProductUrlRewriteGenerator $productUrlRewriteGenerator, - UrlPersistInterface $urlPersist + UrlPersistInterface $urlPersist, + ProductUrlPathGenerator $productUrlPathGenerator = null ) { $this->productUrlRewriteGenerator = $productUrlRewriteGenerator; $this->urlPersist = $urlPersist; + $this->productUrlPathGenerator = $productUrlPathGenerator ?: ObjectManager::getInstance() + ->get(ProductUrlPathGenerator::class); } /** * Generate urls for UrlRewrite and save it in storage + * * @param \Magento\Framework\Event\Observer $observer * @return void + * @throws \Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException */ public function execute(\Magento\Framework\Event\Observer $observer) { @@ -51,6 +66,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) || $product->dataHasChangedFor('visibility') ) { if ($product->isVisibleInSiteVisibility()) { + $product->unsUrlPath(); + $product->setUrlPath($this->productUrlPathGenerator->getUrlPath($product)); $this->urlPersist->replace($this->productUrlRewriteGenerator->generate($product)); } } diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/composer.json b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/composer.json deleted file mode 100644 index ea63772c129dc..0000000000000 --- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-url-rewrite", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-url-rewrite": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php new file mode 100644 index 0000000000000..b12da6243a903 --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/CategoryProcessUrlRewriteMovingObserverTest.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\CatalogUrlRewrite\Test\Unit\Observer; + +use Magento\Catalog\Model\Category; +use Magento\CatalogUrlRewrite\Block\UrlKeyRenderer; +use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator; +use Magento\CatalogUrlRewrite\Model\Map\DatabaseMapPool; +use Magento\CatalogUrlRewrite\Model\Map\DataCategoryUrlRewriteDatabaseMap; +use Magento\CatalogUrlRewrite\Model\Map\DataProductUrlRewriteDatabaseMap; +use Magento\CatalogUrlRewrite\Model\UrlRewriteBunchReplacer; +use Magento\CatalogUrlRewrite\Observer\CategoryProcessUrlRewriteMovingObserver; +use Magento\CatalogUrlRewrite\Observer\UrlRewriteHandler; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\UrlRewrite\Model\UrlPersistInterface; + +/** + * Class CategoryProcessUrlRewriteMovingObserverTest + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class CategoryProcessUrlRewriteMovingObserverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var CategoryProcessUrlRewriteMovingObserver + */ + private $observer; + + /** + * @var CategoryUrlRewriteGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + private $categoryUrlRewriteGeneratorMock; + + /** + * @var UrlPersistInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlPersistMock; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfigMock; + + /** + * @var UrlRewriteHandler|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlRewriteHandlerMock; + + /** + * @var DatabaseMapPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $databaseMapPoolMock; + + /** + * Set Up + */ + protected function setUp() + { + $this->categoryUrlRewriteGeneratorMock = $this->createMock(CategoryUrlRewriteGenerator::class); + $this->urlPersistMock = $this->createMock(UrlPersistInterface::class); + $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); + $this->urlRewriteHandlerMock = $this->createMock(UrlRewriteHandler::class); + /** @var UrlRewriteBunchReplacer|\PHPUnit_Framework_MockObject_MockObject $urlRewriteMock */ + $urlRewriteMock = $this->createMock(UrlRewriteBunchReplacer::class); + $this->databaseMapPoolMock = $this->createMock(DatabaseMapPool::class); + + $this->observer = new CategoryProcessUrlRewriteMovingObserver( + $this->categoryUrlRewriteGeneratorMock, + $this->urlPersistMock, + $this->scopeConfigMock, + $this->urlRewriteHandlerMock, + $urlRewriteMock, + $this->databaseMapPoolMock, + [ + DataCategoryUrlRewriteDatabaseMap::class, + DataProductUrlRewriteDatabaseMap::class + ] + ); + } + + /** + * Test category process rewrite url by changing the parent + * + * @return void + */ + public function testCategoryProcessUrlRewriteAfterMovingWithChangedParentId() + { + /** @var Observer|\PHPUnit_Framework_MockObject_MockObject $observerMock */ + $observerMock = $this->createMock(Observer::class); + $eventMock = $this->getMockBuilder(Event::class) + ->disableOriginalConstructor() + ->setMethods(['getCategory']) + ->getMock(); + $categoryMock = $this->createPartialMock(Category::class, [ + 'dataHasChangedFor', + 'getEntityId', + 'getStoreId', + 'setData' + ]); + + $categoryMock->expects($this->once())->method('dataHasChangedFor')->with('parent_id') + ->willReturn(true); + $eventMock->expects($this->once())->method('getCategory')->willReturn($categoryMock); + $observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $this->scopeConfigMock->expects($this->once())->method('isSetFlag') + ->with(UrlKeyRenderer::XML_PATH_SEO_SAVE_HISTORY)->willReturn(true); + $this->categoryUrlRewriteGeneratorMock->expects($this->once())->method('generate') + ->with($categoryMock, true)->willReturn(['category-url-rewrite']); + $this->urlRewriteHandlerMock->expects($this->once())->method('generateProductUrlRewrites') + ->with($categoryMock)->willReturn(['product-url-rewrite']); + $this->databaseMapPoolMock->expects($this->exactly(2))->method('resetMap')->willReturnSelf(); + + $this->observer->execute($observerMock); + } + + /** + * Test category process rewrite url without changing the parent + * + * @return void + */ + public function testCategoryProcessUrlRewriteAfterMovingWithinNotChangedParent() + { + /** @var Observer|\PHPUnit_Framework_MockObject_MockObject $observerMock */ + $observerMock = $this->createMock(Observer::class); + $eventMock = $this->getMockBuilder(Event::class) + ->disableOriginalConstructor() + ->setMethods(['getCategory']) + ->getMock(); + $categoryMock = $this->createPartialMock(Category::class, ['dataHasChangedFor']); + $observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->once())->method('getCategory')->willReturn($categoryMock); + $categoryMock->expects($this->once())->method('dataHasChangedFor')->with('parent_id') + ->willReturn(false); + + $this->observer->execute($observerMock); + } +} diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json index a1abf32d21a49..66011fcac287e 100644 --- a/app/code/Magento/CatalogUrlRewrite/composer.json +++ b/app/code/Magento/CatalogUrlRewrite/composer.json @@ -14,7 +14,7 @@ "magento/module-ui": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml new file mode 100644 index 0000000000000..ac8beb362f0fb --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/etc/webapi_rest/di.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator" type="Magento\CatalogUrlRewrite\Model\WebapiProductUrlPathGenerator"/> +</config> diff --git a/app/code/Magento/CatalogUrlRewrite/etc/webapi_soap/di.xml b/app/code/Magento/CatalogUrlRewrite/etc/webapi_soap/di.xml new file mode 100644 index 0000000000000..ac8beb362f0fb --- /dev/null +++ b/app/code/Magento/CatalogUrlRewrite/etc/webapi_soap/di.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <preference for="Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator" type="Magento\CatalogUrlRewrite\Model\WebapiProductUrlPathGenerator"/> +</config> diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php index 9a55f981b7607..cb462ada0fc91 100644 --- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php +++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php @@ -164,7 +164,7 @@ public function getCacheKeyInfo() $this->_storeManager->getStore()->getId(), $this->_design->getDesignTheme()->getId(), $this->httpContext->getValue(\Magento\Customer\Model\Context::CONTEXT_GROUP), - intval($this->getRequest()->getParam($this->getData('page_var_name'), 1)), + (int)$this->getRequest()->getParam($this->getData('page_var_name'), 1), $this->getProductsPerPage(), $conditions, $this->json->serialize($this->getRequest()->getParams()), @@ -196,7 +196,7 @@ public function getProductPriceHtml( ? $arguments['display_minimal_price'] : true; - /** @var \Magento\Framework\Pricing\Render $priceRender */ + /** @var \Magento\Framework\Pricing\Render $priceRender */ $priceRender = $this->getLayout()->getBlock('product.price.render.default'); $price = ''; @@ -338,7 +338,7 @@ public function getPagerHtml() if (!$this->pager) { $this->pager = $this->getLayout()->createBlock( \Magento\Catalog\Block\Product\Widget\Html\Pager::class, - 'widget.products.list.pager' + $this->getWidgetPagerBlockName() ); $this->pager->setUseContainer(true) @@ -398,4 +398,19 @@ private function getPriceCurrency() } return $this->priceCurrency; } + + /** + * @return string + */ + private function getWidgetPagerBlockName() + { + $pageName = $this->getData('page_var_name'); + $pagerBlockName = 'widget.products.list.pager'; + + if (!$pageName) { + return $pagerBlockName; + } + + return $pagerBlockName . '.' . $pageName; + } } diff --git a/app/code/Magento/CatalogWidget/Test/Mftf/composer.json b/app/code/Magento/CatalogWidget/Test/Mftf/composer.json deleted file mode 100644 index 5c0548c973170..0000000000000 --- a/app/code/Magento/CatalogWidget/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-catalog-widget", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-rule": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CatalogWidget/composer.json b/app/code/Magento/CatalogWidget/composer.json index 169d21215cb09..496651a6bfa17 100644 --- a/app/code/Magento/CatalogWidget/composer.json +++ b/app/code/Magento/CatalogWidget/composer.json @@ -14,7 +14,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Checkout/Controller/Cart/Delete.php b/app/code/Magento/Checkout/Controller/Cart/Delete.php index 5687e0cad0710..c0371a6e504f6 100644 --- a/app/code/Magento/Checkout/Controller/Cart/Delete.php +++ b/app/code/Magento/Checkout/Controller/Cart/Delete.php @@ -22,7 +22,12 @@ public function execute() $id = (int)$this->getRequest()->getParam('id'); if ($id) { try { - $this->cart->removeItem($id)->save(); + $this->cart->removeItem($id); + // We should set Totals to be recollected once more because of Cart model as usually is loading + // before action executing and in case when triggerRecollect setted as true recollecting will + // executed and the flag will be true already. + $this->cart->getQuote()->setTotalsCollectedFlag(false); + $this->cart->save(); } catch (\Exception $e) { $this->messageManager->addError(__('We can\'t remove the item.')); $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); diff --git a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php index 89eb7bfcce7f1..04cabf3db89c4 100644 --- a/app/code/Magento/Checkout/Model/DefaultConfigProvider.php +++ b/app/code/Magento/Checkout/Model/DefaultConfigProvider.php @@ -13,6 +13,7 @@ use Magento\Customer\Model\Context as CustomerContext; use Magento\Customer\Model\Session as CustomerSession; use Magento\Customer\Model\Url as CustomerUrlManager; +use Magento\Eav\Api\AttributeOptionManagementInterface; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\Http\Context as HttpContext; use Magento\Framework\App\ObjectManager; @@ -26,11 +27,18 @@ use Magento\Store\Model\ScopeInterface; /** + * Default Config Provider. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) * @SuppressWarnings(PHPMD.TooManyFields) */ class DefaultConfigProvider implements ConfigProviderInterface { + /** + * @var AttributeOptionManagementInterface + */ + private $attributeOptionManager; + /** * @var CheckoutHelper */ @@ -194,6 +202,7 @@ class DefaultConfigProvider implements ConfigProviderInterface * @param \Magento\Quote\Api\PaymentMethodManagementInterface $paymentMethodManagement * @param UrlInterface $urlBuilder * @param AddressMetadataInterface $addressMetadata + * @param AttributeOptionManagementInterface $attributeOptionManager * @codeCoverageIgnore * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -224,7 +233,8 @@ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Quote\Api\PaymentMethodManagementInterface $paymentMethodManagement, UrlInterface $urlBuilder, - AddressMetadataInterface $addressMetadata = null + AddressMetadataInterface $addressMetadata = null, + AttributeOptionManagementInterface $attributeOptionManager = null ) { $this->checkoutHelper = $checkoutHelper; $this->checkoutSession = $checkoutSession; @@ -253,10 +263,12 @@ public function __construct( $this->paymentMethodManagement = $paymentMethodManagement; $this->urlBuilder = $urlBuilder; $this->addressMetadata = $addressMetadata ?: ObjectManager::getInstance()->get(AddressMetadataInterface::class); + $this->attributeOptionManager = $attributeOptionManager ?? + ObjectManager::getInstance()->get(AttributeOptionManagementInterface::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function getConfig() { @@ -307,7 +319,7 @@ public function getConfig() } /** - * Is autocomplete enabled for storefront + * Is autocomplete enabled for storefront. * * @return string * @codeCoverageIgnore @@ -321,7 +333,7 @@ private function isAutocompleteEnabled() } /** - * Retrieve customer data + * Retrieve customer data. * * @return array */ @@ -358,11 +370,11 @@ private function filterNotVisibleAttributes(array $attributes) } } - return $attributes; + return $this->setLabelsToAttributes($attributes); } /** - * Set additional customer address data + * Set additional customer address data. * * @param \Magento\Customer\Api\Data\AddressInterface $address * @return string @@ -377,7 +389,7 @@ private function getCustomerAddressInline($address) } /** - * Retrieve quote data + * Retrieve quote data. * * @return array */ @@ -402,7 +414,7 @@ private function getQuoteData() } /** - * Retrieve quote item data + * Retrieve quote item data. * * @return array */ @@ -425,7 +437,7 @@ private function getQuoteItemData() } /** - * Retrieve formatted item options view + * Retrieve formatted item options view. * * @param \Magento\Quote\Api\Data\CartItemInterface $item * @return array @@ -449,7 +461,7 @@ protected function getFormattedOptionValue($item) } /** - * Retrieve customer registration URL + * Retrieve customer registration URL. * * @return string * @codeCoverageIgnore @@ -460,7 +472,7 @@ public function getRegisterUrl() } /** - * Retrieve checkout URL + * Retrieve checkout URL. * * @return string * @codeCoverageIgnore @@ -471,7 +483,7 @@ public function getCheckoutUrl() } /** - * Retrieve checkout URL + * Retrieve checkout URL. * * @return string * @codeCoverageIgnore @@ -482,7 +494,7 @@ public function pageNotFoundUrl() } /** - * Retrieve default success page URL + * Retrieve default success page URL. * * @return string * @codeCoverageIgnore @@ -493,7 +505,7 @@ public function getDefaultSuccessPageUrl() } /** - * Retrieve selected shipping method + * Retrieve selected shipping method. * * @return array|null */ @@ -513,7 +525,7 @@ private function getSelectedShippingMethod() } /** - * Retrieve store code + * Retrieve store code. * * @return string * @codeCoverageIgnore @@ -524,7 +536,7 @@ private function getStoreCode() } /** - * Check if guest checkout is allowed + * Check if guest checkout is allowed. * * @return bool * @codeCoverageIgnore @@ -535,7 +547,7 @@ private function isGuestCheckoutAllowed() } /** - * Check if customer is logged in + * Check if customer is logged in. * * @return bool * @codeCoverageIgnore @@ -546,7 +558,7 @@ private function isCustomerLoggedIn() } /** - * Return forgot password URL + * Return forgot password URL. * * @return string * @codeCoverageIgnore @@ -568,7 +580,8 @@ protected function getStaticBaseUrl() } /** - * Return quote totals data + * Return quote totals data. + * * @return array */ private function getTotalsData() @@ -599,7 +612,8 @@ private function getTotalsData() } /** - * Returns active carriers codes + * Returns active carriers codes. + * * @return array */ private function getActiveCarriers() @@ -612,7 +626,8 @@ private function getActiveCarriers() } /** - * Returns origin country code + * Returns origin country code. + * * @return string */ private function getOriginCountryCode() @@ -625,7 +640,8 @@ private function getOriginCountryCode() } /** - * Returns array of payment methods + * Returns array of payment methods. + * * @return array */ private function getPaymentMethods() @@ -642,4 +658,55 @@ private function getPaymentMethods() } return $paymentMethods; } + + /** + * Set Labels to custom Attributes. + * + * @param array $customAttributes + * @return array + */ + private function setLabelsToAttributes(array $customAttributes) : array + { + if (!empty($customAttributes)) { + foreach ($customAttributes as $customAttributeCode => $customAttribute) { + $attributeOptionLabels = $this->getAttributeLabels($customAttribute, $customAttributeCode); + if (!empty($attributeOptionLabels)) { + $customAttributes[$customAttributeCode]['label'] = implode(', ', $attributeOptionLabels); + } + } + } + + return $customAttributes; + } + + /** + * Get Labels by CustomAttribute and CustomAttributeCode. + * + * @param array $customAttribute + * @param string $customAttributeCode + * @return array + */ + private function getAttributeLabels(array $customAttribute, string $customAttributeCode) : array + { + $attributeOptionLabels = []; + + if (!empty($customAttribute['value'])) { + $customAttributeValues = explode(',', $customAttribute['value']); + $attributeOptions = $this->attributeOptionManager->getItems( + \Magento\Customer\Model\Indexer\Address\AttributeProvider::ENTITY, + $customAttributeCode + ); + + if (!empty($attributeOptions)) { + foreach ($attributeOptions as $attributeOption) { + $attributeOptionValue = $attributeOption->getValue(); + if (in_array($attributeOptionValue, $customAttributeValues)) { + $attributeOptionLabels[] = $attributeOption->getLabel() ?? $attributeOptionValue; + } + } + } + } + + return $attributeOptionLabels; + } } diff --git a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php index 3d6b0aa0cdc12..eca747f9c042f 100644 --- a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php +++ b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php @@ -114,9 +114,8 @@ public function savePaymentInformation( $quote->setDataChanges(true); $shippingAddress = $quote->getShippingAddress(); if ($shippingAddress && $shippingAddress->getShippingMethod()) { - $shippingDataArray = explode('_', $shippingAddress->getShippingMethod()); - $shippingCarrier = array_shift($shippingDataArray); - $shippingAddress->setLimitCarrier($shippingCarrier); + $shippingRate = $shippingAddress->getShippingRateByCode($shippingAddress->getShippingMethod()); + $shippingAddress->setLimitCarrier($shippingRate->getCarrier()); } } $this->paymentMethodManagement->set($cartId, $paymentMethod); diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml index 83dddf0aa8435..db747dbdba657 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/CheckoutActionGroup.xml @@ -11,7 +11,6 @@ <!-- Checkout select Check/Money Order payment --> <actionGroup name="CheckoutSelectCheckMoneyOrderPaymentActionGroup"> <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> - <waitForElementVisible selector="{{CheckoutPaymentSection.billingAddress}}" time="30" stepKey="waitForBillingAddressDetailsAppears"/> <conditionalClick selector="{{CheckoutPaymentSection.checkMoneyOrderPayment}}" dependentSelector="{{CheckoutPaymentSection.checkMoneyOrderPayment}}" visible="true" stepKey="clickCheckMoneyOrderPayment"/> </actionGroup> @@ -49,4 +48,22 @@ <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> </actionGroup> + + <!--Verify country options in checkout top destination section--> + <actionGroup name="VerifyTopDestinationsCountry"> + <arguments> + <argument name="country" type="string"/> + <argument name="placeNumber"/> + </arguments> + <waitForElement selector="{{StorefrontCheckoutCartSummarySection.blockSummary}}" stepKey="waitBlockSummaryLoaded"/> + <conditionalClick selector="{{StorefrontCheckoutCartSummarySection.shippingHeading}}" dependentSelector="{{StorefrontCheckoutCartSummarySection.country}}" visible="false" stepKey="openShippingDetails"/> + <see selector="{{StorefrontCheckoutCartSummarySection.countryParameterized('placeNumber')}}" userInput="{{country}}" stepKey="seeCountry"/> + </actionGroup> + + <!--Click Place Order button--> + <actionGroup name="ClickPlaceOrderActionGroup"> + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <see selector="{{CheckoutSuccessMainSection.successTitle}}" userInput="Thank you for your purchase!" stepKey="waitForLoadSuccessPage"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml index afe37a10e6634..11cd095334113 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillNewBillingAddressActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Guest checkout filling billing section --> <actionGroup name="GuestCheckoutFillNewBillingAddressActionGroup"> <arguments> @@ -25,4 +25,35 @@ <fillField selector="{{CheckoutPaymentSection.guestTelephone}}" userInput="{{customerAddressVar.telephone}}" stepKey="enterTelephone"/> </actionGroup> + <actionGroup name="LoggedInCheckoutFillNewBillingAddressActionGroup"> + <arguments> + <argument name="customerAddress"/> + <!-- the classPrefix argument is to specifically select the inputs of the correct form + this is to prevent having 3 action groups doing essentially the same thing --> + <argument name="classPrefix" type="string" defaultValue=""/> + </arguments> + <fillField stepKey="fillFirstName" selector="{{classPrefix}} {{CheckoutShippingSection.firstName}}" userInput="{{customerAddress.firstname}}"/> + <fillField stepKey="fillLastName" selector="{{classPrefix}} {{CheckoutShippingSection.lastName}}" userInput="{{customerAddress.lastname}}"/> + <fillField stepKey="fillPhoneNumber" selector="{{classPrefix}} {{CheckoutShippingSection.telephone}}" userInput="{{customerAddress.telephone}}"/> + <fillField stepKey="fillStreetAddress1" selector="{{classPrefix}} {{CheckoutShippingSection.street}}" userInput="{{customerAddress.street[0]}}"/> + <fillField stepKey="fillCityName" selector="{{classPrefix}} {{CheckoutShippingSection.city}}" userInput="{{customerAddress.city}}"/> + <selectOption stepKey="selectState" selector="{{classPrefix}} {{CheckoutShippingSection.region}}" userInput="{{customerAddress.state}}"/> + <fillField stepKey="fillZip" selector="{{classPrefix}} {{CheckoutShippingSection.postcode}}" userInput="{{customerAddress.postcode}}"/> + <selectOption stepKey="selectCounty" selector="{{classPrefix}} {{CheckoutShippingSection.country}}" userInput="{{customerAddress.country_id}}"/> + <waitForPageLoad stepKey="waitForFormUpdate2"/> + </actionGroup> + + <actionGroup name="clearCheckoutAddressPopupFieldsActionGroup"> + <arguments> + <argument name="classPrefix" type="string" defaultValue=""/> + </arguments> + <clearField selector="{{classPrefix}} {{CheckoutShippingSection.firstName}}" stepKey="clearFieldFirstName"/> + <clearField selector="{{classPrefix}} {{CheckoutShippingSection.lastName}}" stepKey="clearFieldLastName"/> + <clearField selector="{{classPrefix}} {{CheckoutShippingSection.street}}" stepKey="clearFieldStreetAddress1"/> + <clearField selector="{{classPrefix}} {{CheckoutShippingSection.city}}" stepKey="clearFieldCityName"/> + <selectOption selector="{{classPrefix}} {{CheckoutShippingSection.region}}" userInput="" stepKey="clearFieldRegion"/> + <clearField selector="{{classPrefix}} {{CheckoutShippingSection.postcode}}" stepKey="clearFieldZip"/> + <selectOption selector="{{classPrefix}} {{CheckoutShippingSection.country}}" userInput="" stepKey="clearFieldCounty"/> + <clearField selector="{{classPrefix}} {{CheckoutShippingSection.telephone}}" stepKey="clearFieldPhoneNumber"/> + </actionGroup> </actionGroups> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillingShippingSectionActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillingShippingSectionActionGroup.xml index 9e66e742229bd..8ff84e7a436e5 100644 --- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillingShippingSectionActionGroup.xml +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/GuestCheckoutFillingShippingSectionActionGroup.xml @@ -7,12 +7,14 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!-- Guest checkout filling shipping section --> <actionGroup name="GuestCheckoutFillingShippingSectionActionGroup"> <arguments> - <argument name="customerVar"/> - <argument name="customerAddressVar"/> + <argument name="customerVar" defaultValue="CustomerEntityOne"/> + <argument name="customerAddressVar" defaultValue="CustomerAddressSimple"/> + <!--First available shipping method will be selected if value is not passed for shippingMethod--> + <argument name="shippingMethod" defaultValue="" type="string"/> </arguments> <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{customerVar.email}}" stepKey="enterEmail"/> <fillField selector="{{CheckoutShippingSection.firstName}}" userInput="{{customerVar.firstname}}" stepKey="enterFirstName"/> @@ -23,11 +25,11 @@ <fillField selector="{{CheckoutShippingSection.postcode}}" userInput="{{customerAddressVar.postcode}}" stepKey="enterPostcode"/> <fillField selector="{{CheckoutShippingSection.telephone}}" userInput="{{customerAddressVar.telephone}}" stepKey="enterTelephone"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> - <click selector="{{CheckoutShippingSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('shippingMethod')}}" stepKey="selectShippingMethod"/> <waitForElement selector="{{CheckoutShippingSection.next}}" time="30" stepKey="waitForNextButton"/> <click selector="{{CheckoutShippingSection.next}}" stepKey="clickNext"/> <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" time="30" stepKey="waitForPaymentSectionLoaded"/> <seeInCurrentUrl url="{{CheckoutPage.url}}/#payment" stepKey="assertCheckoutPaymentUrl"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask1"/> </actionGroup> -</actionGroups> \ No newline at end of file +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml new file mode 100644 index 0000000000000..fcbbf537c5d7b --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Data/CountryData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="TopDestinationCountries" type="countryArray"> + <array key="country"> + <item>Bahamas</item> + </array> + </entity> +</entities> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml index 8c896f2cd09de..37079e2bcf144 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml @@ -10,6 +10,6 @@ xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> <page name="CheckoutCartPage" url="/checkout/cart" area="storefront" module="Magento_Checkout"> <section name="CheckoutCartProductSection"/> - <section name="CheckoutCartSummarySection"/> + <section name="StorefrontCheckoutCartSummarySection"/> </page> </pages> diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutPage.xml index f1d792a638746..0c6a1682a7719 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutPage.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutPage.xml @@ -7,7 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="CheckoutPage" url="/checkout" area="storefront" module="Magento_Checkout"> <section name="CheckoutShippingSection"/> <section name="CheckoutShippingMethodsSection"/> @@ -15,5 +15,6 @@ <section name="CheckoutSuccessMainSection"/> <section name="CheckoutPaymentSection"/> <section name="CheckoutGuestShippingInfoSection"/> + <section name="CheckoutAddressPopupSection"/> </page> </pages> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutAddressPopupSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutAddressPopupSection.xml new file mode 100644 index 0000000000000..9e8adab362a88 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutAddressPopupSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="CheckoutAddressPopupSection"> + <element name="newAddressModalPopup" type="block" selector=".modal-popup.modal-slide._inner-scroll"/> + <element name="closeAddressModalPopup" type="button" selector=".action-hide-popup"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml index f9e27ef36c715..c73001cfe6832 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutPaymentSection"> <element name="isPaymentSection" type="text" selector="//*[@class='opc-progress-bar']/li[contains(@class, '_active') and span[contains(.,'Review & Payments')]]"/> <element name="availablePaymentSolutions" type="text" selector="#checkout-payment-method-load>div>div>div:nth-child(2)>div.payment-method-title.field.choice"/> @@ -25,12 +25,13 @@ <element name="cartItems" type="text" selector=".minicart-items"/> <element name="billingAddress" type="text" selector="div.billing-address-details"/> <element name="placeOrder" type="button" selector="button.action.primary.checkout" timeout="30"/> - <element name="productOptionsByProductItemPrice" type="text" selector="//div[@class='product-item-inner']//div[@class='subtotal']//span[@class='price'][contains(.,'{{var1}}')]//ancestor::div[@class='product-item-details']//div[@class='product options']" parameterized="true"/> - <element name="productOptionsActiveByProductItemPrice" type="text" selector="//div[@class='subtotal']//span[@class='price'][contains(.,'{{var1}}')]//ancestor::div[@class='product-item-details']//div[@class='product options active']" parameterized="true"/> + <element name="productOptionsByProductItemPrice" type="text" selector="//div[@class='product-item-inner']//div[@class='subtotal']//span[@class='price'][contains(.,'{{price}}')]//ancestor::div[@class='product-item-details']//div[@class='product options']" parameterized="true"/> + <element name="productOptionsActiveByProductItemPrice" type="text" selector="//div[@class='subtotal']//span[@class='price'][contains(.,'{{price}}')]//ancestor::div[@class='product-item-details']//div[@class='product options active']" parameterized="true"/> + <element name="productItemPriceByName" type="text" selector="//div[@class='product-item-details'][contains(., '{{ProductName}}')]//span[@class='price']" parameterized="true"/> <element name="productOptionsActiveByProductItemName" type="text" selector="//div[@class='product-item-details']//strong[@class='product-item-name'][text()='{{var1}}']//ancestor::div[@class='product-item-details']//div[@class='product options active']" parameterized="true" /> <element name="productOptionsByProductItemName" type="text" selector="//div[@class='product-item-details']//strong[@class='product-item-name'][text()='{{var1}}']//ancestor::div[@class='product-item-details']//div[@class='product options']" parameterized="true" /> <element name="cartItemsArea" type="button" selector=".items-in-cart"/> - <element name="cartItemsAreaActive" type="textarea" selector=".items-in-cart.active"/> + <element name="cartItemsAreaActive" type="textarea" selector=".items-in-cart.active" timeout="30"/> <element name="paymentSectionTitle" type="text" selector="#checkout-payment-method-load .step-title" /> <element name="paymentMethodTitle" type="text" selector=".payment-method-title span" /> <element name="checkMoneyOrderPayment" type="radio" selector="#checkmo.radio" timeout="30"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml index e953c1aab291c..2a7437c44eccf 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml @@ -7,7 +7,7 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="CheckoutShippingSection"> <element name="isShippingStep" type="text" selector="//*[@class='opc-progress-bar']/li[contains(@class, '_active') and span[contains(.,'Shipping')]]"/> <element name="email" type="input" selector="#customer-email"/> @@ -19,13 +19,12 @@ <element name="postcode" type="input" selector="input[name=postcode]"/> <element name="country" type="select" selector="select[name=country_id]"/> <element name="telephone" type="input" selector="input[name=telephone]"/> - <element name="next" type="button" selector="button.button.action.continue.primary"/> <element name="firstShippingMethod" type="radio" selector="#checkout-shipping-method-load input[type='radio']"/> <element name="selectedShippingAddress" type="text" selector=".shipping-address-item.selected-item"/> <element name="newAddressButton" type="button" selector="#checkout-step-shipping button"/> <element name="next" type="button" selector="[data-role='opc-continue']"/> <element name="stateInput" type="input" selector="input[name=region]"/> - <element name="newAdress" type="button" selector="button.action.action-show-popup"/> + <element name="newAdress" type="button" selector="button.action.action-show-popup" timeout="30"/> <element name="addStreet" type="input" selector="#shipping-new-address-form input[name='street[0]']"/> <element name="addCity" type="input" selector="#shipping-new-address-form input[name='city']"/> <element name="addState" type="select" selector="#shipping-new-address-form select[name='region_id']"/> @@ -33,5 +32,6 @@ <element name="addTelephone" type="input" selector="#shipping-new-address-form input[name='telephone']"/> <element name="addcCountry" type="select" selector="#shipping-new-address-form select[name='country_id']"/> <element name="addSaveButton" type="button" selector=".action.primary.action-save-address"/> + <element name="editActiveAddress" type="button" selector="//div[@class='shipping-address-item selected-item']//span[text()='Edit']" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml similarity index 69% rename from app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml rename to app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml index 80f6cfa03d969..a7c5fb484b2bf 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml @@ -5,10 +5,9 @@ * See COPYING.txt for license details. */ --> - <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - <section name="CheckoutCartSummarySection"> + <section name="StorefrontCheckoutCartSummarySection"> <element name="subtotal" type="text" selector="#cart-totals tr.totals.sub span.price"/> <element name="proceedToCheckout" type="button" selector=".action.primary.checkout span" timeout="30"/> <element name="total" type="text" selector=".amount[data-th='Order Total'] span"/> @@ -17,5 +16,10 @@ <element name="postcode" type="text" selector="#block-summary input[name='postcode']" timeout="30"/> <element name="estimateShippingAndTax" type="text" selector="#block-shipping-heading" timeout="5"/> <element name="flatRateShippingMethod" type="radio" selector="#s_method_flatrate_flatrate" timeout="30"/> + <element name="itemDiscount" type="text" selector="tr[class='totals'] td.amount > span"/> + <element name="countryParameterized" type="select" selector="select[name='country_id'] > option:nth-child({{var}})" timeout="10" parameterized="true"/> + <element name="shippingHeading" type="button" selector="#block-shipping-heading"/> + <element name="blockSummary" type="button" selector="#block-summary"/> + <element name="discountAmount" type="text" selector="td[data-th='Discount']"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml index 1ad14129c1612..baf098c855eab 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMinicartSection.xml @@ -10,7 +10,6 @@ xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> <section name="StorefrontMinicartSection"> <element name="productCount" type="text" selector="//header//div[contains(@class, 'minicart-wrapper')]//a[contains(@class, 'showcart')]//span[@class='counter-number']"/> - <element name="viewAndEditCart" type="button" selector="//header//div[contains(@class, 'minicart-wrapper')]//a[contains(@class, 'viewcart')]"/> <element name="productLinkByName" type="button" selector="//header//ol[@id='mini-cart']//div[@class='product-item-details']//a[contains(text(), '{{var1}}')]" parameterized="true"/> <element name="productPriceByName" type="text" selector="//header//ol[@id='mini-cart']//div[@class='product-item-details'][.//a[contains(text(), '{{var1}}')]]//span[@class='price']" parameterized="true"/> <element name="productImageByName" type="text" selector="//header//ol[@id='mini-cart']//span[@class='product-image-container']//img[@alt='{{var1}}']" parameterized="true"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutSpecificDestinationsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutSpecificDestinationsTest.xml new file mode 100644 index 0000000000000..69f63d3548b32 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCheckoutSpecificDestinationsTest.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontCheckoutSpecificDestinationsTest"> + <annotations> + <title value="Check that top destinations can be removed after a selection was previously saved"/> + <stories value="MAGETWO-87971: Top destinations cannot be removed after a selection was previously saved"/> + <description value="Check that top destinations can be removed after a selection was previously saved"/> + <features value="Checkout"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-94906"/> + <group value="Checkout"/> + </annotations> + + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + + <!--Select top destinations countries--> + <actionGroup ref="SelectTopDestinationsCountry" stepKey="selectTopDestinationsCountry"> + <argument name="countries" value="TopDestinationCountries"/> + </actionGroup> + + <!--Go to product page--> + <amOnPage url="{{StorefrontProductPage.url($$createProduct.name$$)}}" stepKey="amOnStorefrontProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + + <!--Add product to cart--> + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToCart"> + <argument name="productName" value="$$createProduct.name$$"/> + </actionGroup> + + <!--Go to shopping cart--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart"/> + <waitForPageLoad stepKey="waitForCheckoutPageLoad"/> + + <!--Verify country options in checkout top destination section--> + <actionGroup ref="VerifyTopDestinationsCountry" stepKey="verifyTopDestinationsCountry"> + <argument name="country" value="Bahamas"/> + <argument name="placeNumber" value="2"/> + </actionGroup> + + <!--Unselect top destinations countries--> + <actionGroup ref="UnSelectTopDestinationsCountry" stepKey="unSelectTopDestinationsCountry"> + <argument name="countries" value="TopDestinationCountries"/> + </actionGroup> + + <!--Go to shopping cart--> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="amOnPageShoppingCart2"/> + <waitForPageLoad stepKey="waitForCheckoutPageLoad2"/> + + <!--Verify country options is shown by default--> + <actionGroup ref="VerifyTopDestinationsCountry" stepKey="verifyTopDestinationsCountry2"> + <argument name="country" value="Afghanistan"/> + <argument name="placeNumber" value="2"/> + </actionGroup> + + <after> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="createCategory" stepKey="deleteProduct"/> + <deleteData createDataKey="createProduct" stepKey="deleteCategory"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithTaxTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithTaxTest.xml index ad26118ac2f06..7dba25812f0dd 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithTaxTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutWithTaxTest.xml @@ -70,7 +70,6 @@ <waitForPageLoad stepKey="waitForPage"/> <!-- Step 7: Click New Address button --> <click selector="{{CheckoutShippingSection.newAdress}}" stepKey="clickAddNewAddress"/> - <waitForPageLoad stepKey="waitForPagePopup"/> <!-- Step 8: Fill form with valid data and set: California --> <!-- Step 9: Click Save Address --> <actionGroup ref="LoggedInUserCheckoutAddNewAddressInShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingAddress"> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml new file mode 100644 index 0000000000000..c80e284633f12 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCustomerPlaceOrderWithNewAddressesThatWasEditedTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout via the Storefront"/> + <title value="Customer Checkout"/> + <description value="Customer can place order with new addresses."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-77181"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="_defaultCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + </before> + <after> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + </after> + <!--Go to Storefront as Customer--> + <actionGroup ref="CustomerLoginOnStorefront" stepKey="customerLogin"> + <argument name="customer" value="$$createCustomer$$" /> + </actionGroup> + + <!-- Add simple product to cart and go to checkout--> + <actionGroup ref="AddSimpleProductToCart" stepKey="addProductToCart"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart" /> + + <!-- Click "+ New Address" and Fill new address--> + <click selector="{{CheckoutShippingSection.newAdress}}" stepKey="addAddress"/> + <actionGroup ref="LoggedInCheckoutFillNewBillingAddressActionGroup" stepKey="changeAddress"> + <argument name="customerAddress" value="UK_Default_Address"/> + <argument name="classPrefix" value="._show"/> + </actionGroup> + + <!--Click "Save Addresses" --> + <click selector="{{CheckoutShippingSection.addSaveButton}}" stepKey="saveAddress"/> + <waitForPageLoad stepKey="waitForAddressSaved"/> + <dontSeeElement selector="{{CheckoutAddressPopupSection.newAddressModalPopup}}" stepKey="dontSeeModalPopup"/> + + <!--Select Shipping Rate "Flat Rate"--> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Flat Rate')}}" stepKey="selectFlatShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask2"/> + + <!--Click "Edit" for the new address and clear required fields--> + <click selector="{{CheckoutShippingSection.editActiveAddress}}" stepKey="editNewAddress"/> + <actionGroup ref="clearCheckoutAddressPopupFieldsActionGroup" stepKey="clearRequiredFields"> + <argument name="classPrefix" value="._show"/> + </actionGroup> + + <!--Close Popup and click next--> + <click selector="{{CheckoutAddressPopupSection.closeAddressModalPopup}}" stepKey="closePopup"/> + <waitForPageLoad stepKey="waitForPopupClosed"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask3"/> + + <!--Refresh Page and Place Order--> + <reloadPage stepKey="reloadPage"/> + <actionGroup ref="ClickPlaceOrderActionGroup" stepKey="placeOrder"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="grabOrderNumber"/> + + <!--Verify New addresses in Customer's Address Book--> + <amOnPage url="{{StorefrontCustomerAddressesPage.url}}" stepKey="goToCustomerAddressBook"/> + <see userInput="{{UK_Default_Address.street[0]}} {{UK_Default_Address.city}}, {{UK_Default_Address.postcode}}" + selector="{{StorefrontCustomerAddressesSection.addressesList}}" stepKey="checkNewAddresses"/> + <!--Order review page has address that was created during checkout--> + <amOnPage url="{{StorefrontCustomerOrderViewPage.url({$grabOrderNumber})}}" stepKey="goToOrderReviewPage"/> + <see userInput="{{UK_Default_Address.street[0]}} {{UK_Default_Address.city}}, {{UK_Default_Address.postcode}}" + selector="{{StorefrontCustomerOrderViewSection.shippingAddress}}" stepKey="checkShippingAddress"/> + <see userInput="{{UK_Default_Address.street[0]}} {{UK_Default_Address.city}}, {{UK_Default_Address.postcode}}" + selector="{{StorefrontCustomerOrderViewSection.billingAddress}}" stepKey="checkBillingAddress"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml index bb6c19ac7fe47..dff957457da95 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml @@ -63,6 +63,7 @@ <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnOrdersPage"/> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearGridFilter"/> <fillField selector="{{OrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="fillOrderNum"/> <click selector="{{OrdersGridSection.submitSearch}}" stepKey="submitSearchOrderNum"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappearOnSearch"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithTaxTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithTaxTest.xml index 518a47eb13d9a..27101a29ed4d3 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithTaxTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutWithTaxTest.xml @@ -66,6 +66,7 @@ <selectOption selector="{{CheckoutShippingGuestInfoSection.region}}" userInput="{{US_Address_NY.state}}" stepKey="selectRegion"/> <!-- Step 9: Go Next --> <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> + <waitForLoadingMaskToDisappear stepKey="waitForNextButtonToBeClear"/> <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> <waitForPageLoad stepKey="waitForPagePlaceOrder"/> <see userInput="$0.84" selector="{{CheckoutPaymentSection.tax}}" stepKey="seeTaxInf"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml new file mode 100644 index 0000000000000..cda8039e505a8 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdatePriceInShoppingCartAfterProductSaveTest.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontUpdatePriceInShoppingCartAfterProductSaveTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout via the Storefront"/> + <title value="Update price in shopping cart after product save"/> + <description value="Price in shopping cart should be updated after product save with changed price"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-77832"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleProduct3" stepKey="createSimpleProduct"> + <field key="price">100</field> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="SetCustomerDataLifetimeActionGroup" stepKey="setCustomerDataLifetime"> + <argument name="minutes" value="1"/> + </actionGroup> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <actionGroup ref="SetCustomerDataLifetimeActionGroup" stepKey="setDefaultCustomerDataLifetime"/> + <magentoCLI command="indexer:reindex customer_grid" stepKey="reindex"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!--Go to product page--> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.custom_attributes[url_key]$$)}}" stepKey="navigateToSimpleProductPage"/> + + <!--Add Product to Shopping Cart--> + <actionGroup ref="addToCartFromStorefrontProductPage" stepKey="addToCartFromStorefrontProductPage"> + <argument name="productName" value="$$createSimpleProduct.name$$"/> + </actionGroup> + + <!--Go to Checkout--> + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"/> + + <!--Check price--> + <conditionalClick selector="{{CheckoutPaymentSection.cartItemsArea}}" dependentSelector="{{CheckoutPaymentSection.cartItemsAreaActive}}" visible="false" stepKey="openItemProductBlock"/> + <see userInput="$100.00" selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" stepKey="checkSummarySubtotal"/> + <see userInput="$100.00" selector="{{CheckoutPaymentSection.productItemPriceByName($$createSimpleProduct.name$$)}}" stepKey="checkItemPrice"/> + + <!--Edit product price via admin panel--> + <openNewTab stepKey="openNewTab"/> + <amOnPage url="{{AdminProductEditPage.url($$createSimpleProduct.id$$)}}" stepKey="goToProductEditPage"/> + <fillField userInput="120" selector="{{AdminProductFormSection.productPrice}}" stepKey="setNewPrice"/> + <actionGroup ref="saveProductForm" stepKey="saveProduct"/> + <closeTab stepKey="closeTab"/> + + <!--Check price--> + <reloadPage stepKey="reloadPage"/> + <waitForPageLoad stepKey="waitForCheckoutPageReload"/> + <conditionalClick selector="{{CheckoutPaymentSection.cartItemsArea}}" dependentSelector="{{CheckoutPaymentSection.cartItemsAreaActive}}" visible="false" stepKey="openItemProductBlock1"/> + <see userInput="$120.00" selector="{{CheckoutPaymentSection.orderSummarySubtotal}}" stepKey="checkSummarySubtotal1"/> + <see userInput="$120.00" selector="{{CheckoutPaymentSection.productItemPriceByName($$createSimpleProduct.name$$)}}" stepKey="checkItemPrice1"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/composer.json b/app/code/Magento/Checkout/Test/Mftf/composer.json deleted file mode 100644 index b76535114bfa6..0000000000000 --- a/app/code/Magento/Checkout/Test/Mftf/composer.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "magento/functional-test-module-checkout", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-msrp": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cookie": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php index b40b2b244ac4c..6ecd759b8b826 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php @@ -168,9 +168,11 @@ private function getMockForAssignBillingAddress($cartId, $billingAddressMock) $billingAddressId = 1; $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); $quoteBillingAddress = $this->createMock(\Magento\Quote\Model\Quote\Address::class); + $shippingRate = $this->createPartialMock(\Magento\Quote\Model\Quote\Address\Rate::class, []); + $shippingRate->setCarrier('flatrate'); $quoteShippingAddress = $this->createPartialMock( \Magento\Quote\Model\Quote\Address::class, - ['setLimitCarrier', 'getShippingMethod'] + ['setLimitCarrier', 'getShippingMethod', 'getShippingRateByCode'] ); $this->cartRepositoryMock->expects($this->any())->method('getActive')->with($cartId)->willReturn($quoteMock); $quoteMock->expects($this->once())->method('getBillingAddress')->willReturn($quoteBillingAddress); @@ -179,6 +181,7 @@ private function getMockForAssignBillingAddress($cartId, $billingAddressMock) $quoteMock->expects($this->once())->method('removeAddress')->with($billingAddressId); $quoteMock->expects($this->once())->method('setBillingAddress')->with($billingAddressMock); $quoteMock->expects($this->once())->method('setDataChanges')->willReturnSelf(); + $quoteShippingAddress->expects($this->any())->method('getShippingRateByCode')->willReturn($shippingRate); $quoteShippingAddress->expects($this->any())->method('getShippingMethod')->willReturn('flatrate_flatrate'); $quoteShippingAddress->expects($this->once())->method('setLimitCarrier')->with('flatrate')->willReturnSelf(); } diff --git a/app/code/Magento/Checkout/composer.json b/app/code/Magento/Checkout/composer.json index 9400af352196c..32cb6c53864db 100644 --- a/app/code/Magento/Checkout/composer.json +++ b/app/code/Magento/Checkout/composer.json @@ -26,7 +26,7 @@ "magento/module-cookie": "100.2.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml index 71b1392d5391f..1005c11e44d95 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml @@ -24,9 +24,9 @@ <?php endif ?> <table id="shopping-cart-table" class="cart items data table" - data-mage-init='{"shoppingCart":{"emptyCartButton": "action.clear", + data-mage-init='{"shoppingCart":{"emptyCartButton": ".action.clear", "updateCartActionContainer": "#update_cart_action_container"}}'> - <caption role="heading" aria-level="2" class="table-caption"><?= /* @escapeNotVerified */ __('Shopping Cart Items') ?></caption> + <caption class="table-caption"><?= /* @escapeNotVerified */ __('Shopping Cart Items') ?></caption> <thead> <tr> <th class="col item" scope="col"><span><?= /* @escapeNotVerified */ __('Item') ?></span></th> diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml index 0567c61f0db60..c96df9cdd3195 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml @@ -111,7 +111,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima </td> </tr> <tr class="item-actions"> - <td colspan="100"> + <td colspan="4"> <div class="actions-toolbar"> <?= /* @escapeNotVerified */ $block->getActions($_item) ?> </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js index 76e3d911e7d3f..54e496131972e 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/cart/estimate-service.js @@ -14,55 +14,71 @@ define([ 'use strict'; var rateProcessors = [], - totalsProcessors = []; + totalsProcessors = [], - quote.shippingAddress.subscribe(function () { - var type = quote.shippingAddress().getType(); + /** + * Estimate totals for shipping address and update shipping rates. + */ + estimateTotalsAndUpdateRates = function () { + var type = quote.shippingAddress().getType(); - if ( - quote.isVirtual() || - window.checkoutConfig.activeCarriers && window.checkoutConfig.activeCarriers.length === 0 - ) { - // update totals block when estimated address was set - totalsProcessors['default'] = totalsDefaultProvider; - totalsProcessors[type] ? - totalsProcessors[type].estimateTotals(quote.shippingAddress()) : - totalsProcessors['default'].estimateTotals(quote.shippingAddress()); - } else { - // check if user data not changed -> load rates from cache - if (!cartCache.isChanged('address', quote.shippingAddress()) && - !cartCache.isChanged('cartVersion', customerData.get('cart')()['data_id']) && - cartCache.get('rates') + if ( + quote.isVirtual() || + window.checkoutConfig.activeCarriers && window.checkoutConfig.activeCarriers.length === 0 ) { - shippingService.setShippingRates(cartCache.get('rates')); + // update totals block when estimated address was set + totalsProcessors['default'] = totalsDefaultProvider; + totalsProcessors[type] ? + totalsProcessors[type].estimateTotals(quote.shippingAddress()) : + totalsProcessors['default'].estimateTotals(quote.shippingAddress()); + } else { + // check if user data not changed -> load rates from cache + if (!cartCache.isChanged('address', quote.shippingAddress()) && + !cartCache.isChanged('cartVersion', customerData.get('cart')()['data_id']) && + cartCache.get('rates') + ) { + shippingService.setShippingRates(cartCache.get('rates')); - return; + return; + } + + // update rates list when estimated address was set + rateProcessors['default'] = defaultProcessor; + rateProcessors[type] ? + rateProcessors[type].getRates(quote.shippingAddress()) : + rateProcessors['default'].getRates(quote.shippingAddress()); + + // save rates to cache after load + shippingService.getShippingRates().subscribe(function (rates) { + cartCache.set('rates', rates); + }); } + }, - // update rates list when estimated address was set - rateProcessors['default'] = defaultProcessor; - rateProcessors[type] ? - rateProcessors[type].getRates(quote.shippingAddress()) : - rateProcessors['default'].getRates(quote.shippingAddress()); + /** + * Estimate totals for shipping address. + */ + estimateTotalsShipping = function () { + totalsDefaultProvider.estimateTotals(quote.shippingAddress()); + }, - // save rates to cache after load - shippingService.getShippingRates().subscribe(function (rates) { - cartCache.set('rates', rates); - }); - } - }); - quote.shippingMethod.subscribe(function () { - totalsDefaultProvider.estimateTotals(quote.shippingAddress()); - }); - quote.billingAddress.subscribe(function () { - var type = quote.billingAddress().getType(); + /** + * Estimate totals for billing address. + */ + estimateTotalsBilling = function () { + var type = quote.billingAddress().getType(); + + if (quote.isVirtual()) { + // update totals block when estimated address was set + totalsProcessors['default'] = totalsDefaultProvider; + totalsProcessors[type] ? + totalsProcessors[type].estimateTotals(quote.billingAddress()) : + totalsProcessors['default'].estimateTotals(quote.billingAddress()); + } + }; - if (quote.isVirtual()) { - // update totals block when estimated address was set - totalsProcessors['default'] = totalsDefaultProvider; - totalsProcessors[type] ? - totalsProcessors[type].estimateTotals(quote.billingAddress()) : - totalsProcessors['default'].estimateTotals(quote.billingAddress()); - } - }); + quote.shippingAddress.subscribe(estimateTotalsAndUpdateRates); + quote.shippingMethod.subscribe(estimateTotalsShipping); + quote.billingAddress.subscribe(estimateTotalsBilling); + customerData.get('cart').subscribe(estimateTotalsShipping); }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js b/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js index 73f4df567903c..28e04699f8daf 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js @@ -218,16 +218,31 @@ define([ * Apply resolved billing address to quote */ applyBillingAddress: function () { - var shippingAddress; + var shippingAddress, + isBillingAddressInitialized; if (quote.billingAddress()) { selectBillingAddress(quote.billingAddress()); return; } + + if (quote.isVirtual()) { + isBillingAddressInitialized = addressList.some(function (addrs) { + if (addrs.isDefaultBilling()) { + selectBillingAddress(addrs); + + return true; + } + + return false; + }); + } + shippingAddress = quote.shippingAddress(); - if (shippingAddress && + if (!isBillingAddressInitialized && + shippingAddress && shippingAddress.canUseForBilling() && (shippingAddress.isDefaultShipping() || !quote.isVirtual()) ) { diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js b/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js index 848a7daf71e1b..16fc459729bbc 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/error-processor.js @@ -8,8 +8,9 @@ */ define([ 'mage/url', - 'Magento_Ui/js/model/messageList' -], function (url, globalMessageList) { + 'Magento_Ui/js/model/messageList', + 'consoleLogger' +], function (url, globalMessageList, consoleLogger) { 'use strict'; return { @@ -25,8 +26,12 @@ define([ if (response.status == 401) { //eslint-disable-line eqeqeq window.location.replace(url.build('customer/account/login/')); } else { - error = JSON.parse(response.responseText); - messageContainer.addErrorMessage(error); + try { + error = JSON.parse(response.responseText); + messageContainer.addErrorMessage(error); + } catch (e) { + consoleLogger.error(e); + } } } }; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js b/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js index e4b1e464348b9..db49683129f2b 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/progress-bar.js @@ -24,11 +24,17 @@ define([ /** @inheritdoc */ initialize: function () { + var stepsValue; + this._super(); $(window).hashchange(_.bind(stepNavigator.handleHash, stepNavigator)); if (!window.location.hash) { - stepNavigator.setHash(stepNavigator.steps().sort(stepNavigator.sortItems)[0].code); + stepsValue = stepNavigator.steps(); + + if (stepsValue.length) { + stepNavigator.setHash(stepsValue.sort(stepNavigator.sortItems)[0].code); + } } stepNavigator.handleHash(); diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html index fd994a4e8a955..ea521b3a8afd4 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address/details.html @@ -4,28 +4,37 @@ * See COPYING.txt for license details. */ --> -<div class="billing-address-details" data-bind="if: isAddressDetailsVisible() && currentBillingAddress()"> - <!-- ko text: currentBillingAddress().prefix --><!-- /ko --> <!-- ko text: currentBillingAddress().firstname --><!-- /ko --> <!-- ko text: currentBillingAddress().middlename --><!-- /ko --> - <!-- ko text: currentBillingAddress().lastname --><!-- /ko --> <!-- ko text: currentBillingAddress().suffix --><!-- /ko --><br/> - <!-- ko text: _.values(currentBillingAddress().street).join(", ") --><!-- /ko --><br/> - <!-- ko text: currentBillingAddress().city --><!-- /ko -->, <span data-bind="html: currentBillingAddress().region"></span> <!-- ko text: currentBillingAddress().postcode --><!-- /ko --><br/> - <!-- ko text: getCountryName(currentBillingAddress().countryId) --><!-- /ko --><br/> - <!-- ko if: (currentBillingAddress().telephone) --> - <a data-bind="text: currentBillingAddress().telephone, attr: {'href': 'tel:' + currentBillingAddress().telephone}"></a> - <!-- /ko --><br/> - <!-- ko foreach: { data: currentBillingAddress().customAttributes, as: 'element' } --> - <!-- ko foreach: { data: Object.keys(element), as: 'attribute' } --> - <!-- ko if: (typeof element[attribute] === "object") --> - <!-- ko text: element[attribute].value --><!-- /ko --> - <!-- /ko --> - <!-- ko if: (typeof element[attribute] === "string") --> - <!-- ko text: element[attribute] --><!-- /ko --> - <!-- /ko --><br/> - <!-- /ko --> - <!-- /ko --> - <button type="button" +<div if="isAddressDetailsVisible() && currentBillingAddress()" class="billing-address-details"> + <text args="currentBillingAddress().prefix"/> <text args="currentBillingAddress().firstname"/> <text args="currentBillingAddress().middlename"/> + <text args="currentBillingAddress().lastname"/> <text args="currentBillingAddress().suffix"/><br/> + <text args="_.values(currentBillingAddress().street).join(', ')"/><br/> + <text args="currentBillingAddress().city "/>, <span text="currentBillingAddress().region"></span> <text args="currentBillingAddress().postcode"/><br/> + <text args="getCountryName(currentBillingAddress().countryId)"/><br/> + <a if="currentBillingAddress().telephone" attr="'href': 'tel:' + currentBillingAddress().telephone" text="currentBillingAddress().telephone"></a><br/> + + <each args="data: currentBillingAddress().customAttributes, as: 'element'"> + <each args="data: Object.keys(element), as: 'attribute'"> + <if args="typeof element[attribute] === 'object'"> + <if args="element[attribute].label"> + <text args="element[attribute].label"/> + </if> + <ifnot args="element[attribute].label"> + <if args="element[attribute].value"> + <text args="element[attribute].value"/> + </if> + </ifnot> + </if> + <if args="typeof element[attribute] === 'string'"> + <text args="element[attribute]"/> + </if><br/> + </each> + </each> + + <button visible="!isAddressSameAsShipping()" + type="button" class="action action-edit-address" - data-bind="visible: !isAddressSameAsShipping(), click: editAddress"> - <span data-bind="i18n: 'Edit'"></span> + click="editAddress"> + <span translate="'Edit'"></span> </button> </div> + diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html index 2e268461d1eea..2a5dc27328a43 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-address/address-renderer/default.html @@ -4,33 +4,38 @@ * See COPYING.txt for license details. */ --> -<div class="shipping-address-item" data-bind="css: isSelected() ? 'selected-item' : 'not-selected-item'"> - <!-- ko text: address().prefix --><!-- /ko --> <!-- ko text: address().firstname --><!-- /ko --> <!-- ko text: address().middlename --><!-- /ko --> - <!-- ko text: address().lastname --><!-- /ko --> <!-- ko text: address().suffix --><!-- /ko --><br/> - <!-- ko text: _.values(address().street).join(", ") --><!-- /ko --><br/> - <!-- ko text: address().city --><!-- /ko -->, <span data-bind="html: address().region"></span> <!-- ko text: address().postcode --><!-- /ko --><br/> - <!-- ko text: getCountryName(address().countryId) --><!-- /ko --><br/> - <!-- ko if: (address().telephone) --> - <a data-bind="text: address().telephone, attr: {'href': 'tel:' + address().telephone}"></a> - <!-- /ko --><br/> - <!-- ko foreach: { data: address().customAttributes, as: 'element' } --> - <!-- ko foreach: { data: Object.keys(element), as: 'attribute' } --> - <!-- ko if: (typeof element[attribute] === "object") --> - <!-- ko text: element[attribute].value --><!-- /ko --> - <!-- /ko --> - <!-- ko if: (typeof element[attribute] === "string") --> - <!-- ko text: element[attribute] --><!-- /ko --> - <!-- /ko --><br/> - <!-- /ko --> - <!-- /ko --> - <!-- ko if: (address().isEditable()) --> - <button type="button" +<div class="shipping-address-item" css="'selected-item' : isSelected() , 'not-selected-item':!isSelected()"> + <text args="address().prefix"/> <text args="address().firstname"/> <text args="address().middlename"/> + <text args="address().lastname"/> <text args="address().suffix"/><br/> + <text args="_.values(address().street).join(', ')"/><br/> + <text args="address().city "/>, <span text="address().region"></span> <text args="address().postcode"/><br/> + <text args="getCountryName(address().countryId)"/><br/> + <a if="address().telephone" attr="'href': 'tel:' + address().telephone" text="address().telephone"></a><br/> + + <each args="data: address().customAttributes, as: 'element'"> + <each args="data: Object.keys(element), as: 'attribute'"> + <if args="typeof element[attribute] === 'object'"> + <if args="element[attribute].label"> + <text args="element[attribute].label"/> + </if> + <ifnot args="element[attribute].label"> + <if args="element[attribute].value"> + <text args="element[attribute].value"/> + </if> + </ifnot> + </if> + <if args="typeof element[attribute] === 'string'"> + <text args="element[attribute]"/> + </if><br/> + </each> + </each> + + <button visible="address().isEditable()" type="button" class="action edit-address-link" - data-bind="click: editAddress, visible: address().isEditable()"> - <span data-bind="i18n: 'Edit'"></span> + click="editAddress"> + <span translate="'Edit'"></span> </button> - <!-- /ko --> - <button type="button" data-bind="click: selectAddress" class="action action-select-shipping-item"> - <span data-bind="i18n: 'Ship Here'"></span> + <button type="button" click="selectAddress" class="action action-select-shipping-item"> + <span translate="'Ship Here'"></span> </button> </div> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html index b66526f660af7..541413955cb47 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/shipping-information/address-renderer/default.html @@ -4,23 +4,29 @@ * See COPYING.txt for license details. */ --> -<!-- ko if: (visible()) --> - <!-- ko text: address().prefix --><!-- /ko --> <!-- ko text: address().firstname --><!-- /ko --> <!-- ko text: address().middlename --><!-- /ko --> - <!-- ko text: address().lastname --><!-- /ko --> <!-- ko text: address().suffix --><!-- /ko --><br/> - <!-- ko text: _.values(address().street).join(", ") --><!-- /ko --><br/> - <!-- ko text: address().city --><!-- /ko -->, <span data-bind="html: address().region"></span> <!-- ko text: address().postcode --><!-- /ko --><br/> - <!-- ko text: getCountryName(address().countryId) --><!-- /ko --><br/> - <!-- ko if: (address().telephone) --> - <a data-bind="text: address().telephone, attr: {'href': 'tel:' + address().telephone}"></a> - <!-- /ko --><br/> - <!-- ko foreach: { data: address().customAttributes, as: 'element' } --> - <!-- ko foreach: { data: Object.keys(element), as: 'attribute' } --> - <!-- ko if: (typeof element[attribute] === "object") --> - <!-- ko text: element[attribute].value --><!-- /ko --> - <!-- /ko --> - <!-- ko if: (typeof element[attribute] === "string") --> - <!-- ko text: element[attribute] --><!-- /ko --> - <!-- /ko --><br/> - <!-- /ko --> - <!-- /ko --> -<!-- /ko --> +<if args="visible()"> + <text args="address().prefix"/> <text args="address().firstname"/> <text args="address().middlename"/> + <text args="address().lastname"/> <text args="address().suffix"/><br/> + <text args="_.values(address().street).join(', ')"/><br/> + <text args="address().city "/>, <span text="address().region"></span> <text args="address().postcode"/><br/> + <text args="getCountryName(address().countryId)"/><br/> + <a if="address().telephone" attr="'href': 'tel:' + address().telephone" text="address().telephone"></a><br/> + + <each args="data: address().customAttributes, as: 'element'"> + <each args="data: Object.keys(element), as: 'attribute'"> + <if args="typeof element[attribute] === 'object'"> + <if args="element[attribute].label"> + <text args="element[attribute].label"/> + </if> + <ifnot args="element[attribute].label"> + <if args="element[attribute].value"> + <text args="element[attribute].value"/> + </if> + </ifnot> + </if> + <if args="typeof element[attribute] === 'string'"> + <text args="element[attribute]"/> + </if><br/> + </each> + </each> +</if> diff --git a/app/code/Magento/CheckoutAgreements/Test/Mftf/composer.json b/app/code/Magento/CheckoutAgreements/Test/Mftf/composer.json deleted file mode 100644 index c4b8e1fe51c75..0000000000000 --- a/app/code/Magento/CheckoutAgreements/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-checkout-agreements", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CheckoutAgreements/composer.json b/app/code/Magento/CheckoutAgreements/composer.json index d54b90d823386..a53558981e2f8 100644 --- a/app/code/Magento/CheckoutAgreements/composer.json +++ b/app/code/Magento/CheckoutAgreements/composer.json @@ -10,7 +10,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php index 4b8a0f3494dd9..2cd1647a1bf22 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -500,14 +500,6 @@ public function uploadFile($targetPath, $type = null) // create thumbnail $this->resizeFile($targetPath . '/' . $uploader->getUploadedFileName(), true); - $result['cookie'] = [ - 'name' => $this->getSession()->getName(), - 'value' => $this->getSession()->getSessionId(), - 'lifetime' => $this->getSession()->getCookieLifetime(), - 'path' => $this->getSession()->getCookiePath(), - 'domain' => $this->getSession()->getCookieDomain(), - ]; - return $result; } diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml index d0f72da39c482..39face57e5c86 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminCreateCmsPageTest.xml @@ -17,8 +17,9 @@ <severity value="CRITICAL"/> <testCaseId value="MAGETWO-25580"/> <group value="cms"/> - <!-- MQE-282 --> - <group value="skip"/> + <skip> + <issueId value="MQE-282"/> + </skip> </annotations> <after> <amOnPage url="admin/admin/auth/logout/" stepKey="amOnLogoutPage"/> diff --git a/app/code/Magento/Cms/Test/Mftf/composer.json b/app/code/Magento/Cms/Test/Mftf/composer.json deleted file mode 100644 index 2d0c6274fd0d1..0000000000000 --- a/app/code/Magento/Cms/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-cms", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-variable": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-cms-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php index 634a51272b39b..906a7d4fbc605 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php @@ -437,14 +437,7 @@ public function testUploadFile() $thumbnailDestination = $thumbnailTargetPath . '/' . $fileName; $type = 'image'; $result = [ - 'result', - 'cookie' => [ - 'name' => 'session_name', - 'value' => '1', - 'lifetime' => '50', - 'path' => 'cookie/path', - 'domain' => 'cookie_domain', - ] + 'result' ]; $uploader = $this->getMockBuilder(\Magento\MediaStorage\Model\File\Uploader::class) ->disableOriginalConstructor() @@ -504,17 +497,6 @@ public function testUploadFile() $this->adapterFactoryMock->expects($this->atLeastOnce())->method('create')->willReturn($image); - $this->sessionMock->expects($this->atLeastOnce())->method('getName') - ->willReturn($result['cookie']['name']); - $this->sessionMock->expects($this->atLeastOnce())->method('getSessionId') - ->willReturn($result['cookie']['value']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookieLifetime') - ->willReturn($result['cookie']['lifetime']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookiePath') - ->willReturn($result['cookie']['path']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookieDomain') - ->willReturn($result['cookie']['domain']); - $this->assertEquals($result, $this->imagesStorage->uploadFile($targetPath, $type)); } } diff --git a/app/code/Magento/Cms/composer.json b/app/code/Magento/Cms/composer.json index ea31f1bda59b2..64e97e0a38e18 100644 --- a/app/code/Magento/Cms/composer.json +++ b/app/code/Magento/Cms/composer.json @@ -18,7 +18,7 @@ "magento/module-cms-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "102.0.5", + "version": "102.0.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Cms/view/adminhtml/layout/cms_wysiwyg_images_index.xml b/app/code/Magento/Cms/view/adminhtml/layout/cms_wysiwyg_images_index.xml index 1bc8828ef6c8e..5bf66ef302f04 100644 --- a/app/code/Magento/Cms/view/adminhtml/layout/cms_wysiwyg_images_index.xml +++ b/app/code/Magento/Cms/view/adminhtml/layout/cms_wysiwyg_images_index.xml @@ -9,7 +9,11 @@ <container name="root"> <block class="Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content" name="wysiwyg_images.content" template="Magento_Cms::browser/content.phtml"> <block class="Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Tree" name="wysiwyg_images.tree" template="Magento_Cms::browser/tree.phtml"/> - <block class="Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content\Uploader" name="wysiwyg_images.uploader" template="Magento_Cms::browser/content/uploader.phtml"/> + <block class="Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content\Uploader" name="wysiwyg_images.uploader" template="Magento_Cms::browser/content/uploader.phtml"> + <arguments> + <argument name="image_upload_config_data" xsi:type="object">Magento\Backend\Block\DataProviders\UploadConfig</argument> + </arguments> + </block> </block> </container> </layout> diff --git a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml index f87dc07dc04f4..27f14434b5fcf 100644 --- a/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml +++ b/app/code/Magento/Cms/view/adminhtml/templates/browser/content/uploader.phtml @@ -7,6 +7,14 @@ // @codingStandardsIgnoreFile /** @var $block \Magento\Cms\Block\Adminhtml\Wysiwyg\Images\Content\Uploader */ + +$resizeConfig = $block->getImageUploadConfigData()->getIsResizeEnabled() + ? "{action: 'resize', maxWidth: " + . $block->getImageUploadMaxWidth() + . ", maxHeight: " + . $block->getImageUploadMaxHeight() + . "}" + : "{action: 'resize'}"; ?> <div id="<?= $block->getHtmlId() ?>" class="uploader"> @@ -99,11 +107,9 @@ require([ action: 'load', fileTypes: /^image\/(gif|jpeg|png)$/, maxFileSize: <?= (int) $block->getFileSizeService()->getMaxFileSize() ?> * 10 - }, { - action: 'resize', - maxWidth: <?= (int) $block->getImageUploadMaxWidth() ?> , - maxHeight: <?= (int) $block->getImageUploadMaxHeight() ?> - }, { + }, + <?= $block->escapeHtml($resizeConfig) ?>, + { action: 'save' }] }); diff --git a/app/code/Magento/CmsUrlRewrite/Test/Mftf/composer.json b/app/code/Magento/CmsUrlRewrite/Test/Mftf/composer.json deleted file mode 100644 index ba91c4d901431..0000000000000 --- a/app/code/Magento/CmsUrlRewrite/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-cms-url-rewrite", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-url-rewrite": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CmsUrlRewrite/composer.json b/app/code/Magento/CmsUrlRewrite/composer.json index e86f4c073db74..414dcdf54921d 100644 --- a/app/code/Magento/CmsUrlRewrite/composer.json +++ b/app/code/Magento/CmsUrlRewrite/composer.json @@ -9,7 +9,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php index f5d109b198d5a..83c61f90f789a 100644 --- a/app/code/Magento/Config/App/Config/Type/System.php +++ b/app/code/Magento/Config/App/Config/Type/System.php @@ -11,6 +11,8 @@ use Magento\Framework\App\Config\Spi\PreProcessorInterface; use Magento\Framework\App\ObjectManager; use Magento\Config\App\Config\Type\System\Reader; +use Magento\Framework\Serialize\Serializer\Sensitive as SensitiveSerializer; +use Magento\Framework\Serialize\Serializer\SensitiveFactory as SensitiveSerializerFactory; use Magento\Framework\App\ScopeInterface; use Magento\Framework\Cache\FrontendInterface; use Magento\Framework\Serialize\SerializerInterface; @@ -44,7 +46,7 @@ class System implements ConfigTypeInterface private $cache; /** - * @var SerializerInterface + * @var SensitiveSerializer */ private $serializer; @@ -79,7 +81,9 @@ class System implements ConfigTypeInterface * @param int $cachingNestedLevel * @param string $configType * @param Reader $reader + * @param SensitiveSerializerFactory|null $sensitiveFactory * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( @@ -91,13 +95,21 @@ public function __construct( PreProcessorInterface $preProcessor, $cachingNestedLevel = 1, $configType = self::CONFIG_TYPE, - Reader $reader = null + Reader $reader = null, + SensitiveSerializerFactory $sensitiveFactory = null ) { $this->postProcessor = $postProcessor; $this->cache = $cache; - $this->serializer = $serializer; $this->configType = $configType; - $this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class); + $this->reader = $reader ?: ObjectManager::getInstance() + ->get(Reader::class); + $sensitiveFactory = $sensitiveFactory ?? ObjectManager::getInstance() + ->get(SensitiveSerializerFactory::class); + //Using sensitive serializer because any kind of information may + //be stored in configs. + $this->serializer = $sensitiveFactory->create( + ['serializer' => $serializer] + ); } /** diff --git a/app/code/Magento/Config/Block/System/Config/Form/Field/Datetime.php b/app/code/Magento/Config/Block/System/Config/Form/Field/Datetime.php index 63dbb2b80e334..acf830f363ce6 100644 --- a/app/code/Magento/Config/Block/System/Config/Form/Field/Datetime.php +++ b/app/code/Magento/Config/Block/System/Config/Form/Field/Datetime.php @@ -40,7 +40,7 @@ public function __construct( protected function _getElementHtml(AbstractElement $element) { return $this->dateTimeFormatter->formatObject( - $this->_localeDate->date(intval($element->getValue())), + $this->_localeDate->date((int) $element->getValue()), $this->_localeDate->getDateTimeFormat(\IntlDateFormatter::MEDIUM) ); } diff --git a/app/code/Magento/Config/Block/System/Config/Form/Field/Notification.php b/app/code/Magento/Config/Block/System/Config/Form/Field/Notification.php index 7f21bf4b92bf4..40ff76ee0e885 100644 --- a/app/code/Magento/Config/Block/System/Config/Form/Field/Notification.php +++ b/app/code/Magento/Config/Block/System/Config/Form/Field/Notification.php @@ -44,6 +44,6 @@ protected function _getElementHtml(AbstractElement $element) $format = $this->_localeDate->getDateTimeFormat( \IntlDateFormatter::MEDIUM ); - return $this->dateTimeFormatter->formatObject($this->_localeDate->date(intval($element->getValue())), $format); + return $this->dateTimeFormatter->formatObject($this->_localeDate->date((int)$element->getValue()), $format); } } diff --git a/app/code/Magento/Config/Console/Command/ConfigShow/ValueProcessor.php b/app/code/Magento/Config/Console/Command/ConfigShow/ValueProcessor.php index 582f87508089f..aeb57010e4969 100644 --- a/app/code/Magento/Config/Console/Command/ConfigShow/ValueProcessor.php +++ b/app/code/Magento/Config/Console/Command/ConfigShow/ValueProcessor.php @@ -97,7 +97,7 @@ public function process($scope, $scopeCode, $value, $path) $field = $configStructure->getElementByConfigPath($path); /** @var Value $backendModel */ - $backendModel = $field && $field->hasBackendModel() + $backendModel = $field instanceof Field && $field->hasBackendModel() ? $field->getBackendModel() : $this->configValueFactory->create(); diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php index c6e2412f7e58f..0472c5daa276f 100644 --- a/app/code/Magento/Config/Model/Config.php +++ b/app/code/Magento/Config/Model/Config.php @@ -237,13 +237,14 @@ private function getField(string $sectionId, string $groupId, string $fieldId): * Get field path * * @param Field $field + * @param string $fieldId Need for support of clone_field feature * @param array &$oldConfig Need for compatibility with _processGroup() * @param array &$extraOldGroups Need for compatibility with _processGroup() * @return string */ - private function getFieldPath(Field $field, array &$oldConfig, array &$extraOldGroups): string + private function getFieldPath(Field $field, string $fieldId, array &$oldConfig, array &$extraOldGroups): string { - $path = $field->getGroupPath() . '/' . $field->getId(); + $path = $field->getGroupPath() . '/' . $fieldId; /** * Look for custom defined field path @@ -303,7 +304,7 @@ private function getChangedPaths( if (isset($groupData['fields'])) { foreach ($groupData['fields'] as $fieldId => $fieldData) { $field = $this->getField($sectionId, $groupId, $fieldId); - $path = $this->getFieldPath($field, $oldConfig, $extraOldGroups); + $path = $this->getFieldPath($field, $fieldId, $oldConfig, $extraOldGroups); if ($this->isValueChanged($oldConfig, $path, $fieldData)) { $changedPaths[] = $path; } @@ -398,7 +399,7 @@ protected function _processGroup( $backendModel->addData($data); $this->_checkSingleStoreMode($field, $backendModel); - $path = $this->getFieldPath($field, $extraOldGroups, $oldConfig); + $path = $this->getFieldPath($field, $fieldId, $extraOldGroups, $oldConfig); $backendModel->setPath($path)->setValue($fieldData['value']); $inherit = !empty($fieldData['inherit']); diff --git a/app/code/Magento/Config/Model/Config/Backend/Currency/Cron.php b/app/code/Magento/Config/Model/Config/Backend/Currency/Cron.php index 3f80e01802b8d..e0e1525fd04cc 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Currency/Cron.php +++ b/app/code/Magento/Config/Model/Config/Backend/Currency/Cron.php @@ -59,14 +59,14 @@ public function afterSave() $frequencyMonthly = \Magento\Cron\Model\Config\Source\Frequency::CRON_MONTHLY; $cronExprArray = [ - intval($time[1]), # Minute - intval($time[0]), # Hour + (int)$time[1], # Minute + (int)$time[0], # Hour $frequency == $frequencyMonthly ? '1' : '*', # Day of the Month '*', # Month of the Year $frequency == $frequencyWeekly ? '1' : '*', # Day of the Week ]; - $cronExprString = join(' ', $cronExprArray); + $cronExprString = implode(' ', $cronExprArray); try { /** @var $configValue \Magento\Framework\App\Config\ValueInterface */ diff --git a/app/code/Magento/Config/Model/Config/Backend/Log/Cron.php b/app/code/Magento/Config/Model/Config/Backend/Log/Cron.php index 3c36baf6f31f4..163124eae1204 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Log/Cron.php +++ b/app/code/Magento/Config/Model/Config/Backend/Log/Cron.php @@ -73,13 +73,13 @@ public function afterSave() if ($enabled) { $cronExprArray = [ - intval($time[1]), # Minute - intval($time[0]), # Hour + (int)$time[1], # Minute + (int)$time[0], # Hour $frequency == $frequencyMonthly ? '1' : '*', # Day of the Month '*', # Month of the Year $frequency == $frequencyWeekly ? '1' : '*', # Day of the Week ]; - $cronExprString = join(' ', $cronExprArray); + $cronExprString = implode(' ', $cronExprArray); } else { $cronExprString = ''; } diff --git a/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php b/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php index 2733847bab1d0..e615678550108 100644 --- a/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php +++ b/app/code/Magento/Config/Model/Config/Structure/Mapper/Sorting.php @@ -62,10 +62,6 @@ protected function _cmp($elementA, $elementB) $sortIndexB = (float)$elementB['sortOrder']; } - if ($sortIndexA == $sortIndexB) { - return 0; - } - - return $sortIndexA < $sortIndexB ? -1 : 1; + return $sortIndexA <=> $sortIndexB; } } diff --git a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml index d67f0c3f243d8..18a124ac669ec 100644 --- a/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml +++ b/app/code/Magento/Config/Test/Mftf/Section/GeneralSection.xml @@ -15,7 +15,7 @@ <element name="EnableWYSIWYG" type="button" selector="#cms_wysiwyg_enabled"/> <element name="SwitcherSystemValue" type="button" selector="#cms_wysiwyg_editor_inherit"/> <element name="Switcher" type="button" selector="#cms_wysiwyg_editor" /> - <element name="Save" type="button" selector="#save"/> + <element name="Save" type="button" selector="#save" timeout="30"/> </section> <section name="WebSection"> <element name="DefaultLayoutsTab" type="button" selector="#web_default_layouts-head"/> diff --git a/app/code/Magento/Config/Test/Mftf/composer.json b/app/code/Magento/Config/Test/Mftf/composer.json deleted file mode 100644 index a464a84b98d85..0000000000000 --- a/app/code/Magento/Config/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-config", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-cron": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-deploy": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php b/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php index 40aa110382ede..9ca4e6138babe 100644 --- a/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php +++ b/app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php @@ -11,6 +11,8 @@ use Magento\Framework\App\Config\Spi\PostProcessorInterface; use Magento\Framework\App\Config\Spi\PreProcessorInterface; use Magento\Framework\Cache\FrontendInterface; +use Magento\Framework\Serialize\Serializer\Sensitive; +use Magento\Framework\Serialize\Serializer\SensitiveFactory; use Magento\Framework\Serialize\SerializerInterface; use Magento\Store\Model\Config\Processor\Fallback; use Magento\Config\App\Config\Type\System\Reader; @@ -74,22 +76,34 @@ public function setUp() ->getMockForAbstractClass(); $this->preProcessor = $this->getMockBuilder(PreProcessorInterface::class) ->getMockForAbstractClass(); - $this->serializer = $this->getMockBuilder(SerializerInterface::class) + $this->serializer = $this->getMockBuilder(Sensitive::class) + ->disableOriginalConstructor() ->getMock(); $this->reader = $this->getMockBuilder(Reader::class) ->disableOriginalConstructor() ->getMock(); + $sensitiveFactory = $this->getMockBuilder(SensitiveFactory::class) + ->disableOriginalConstructor() + ->getMock(); + $sensitiveFactory->expects($this->any()) + ->method('create') + ->willReturn($this->serializer); + /** @var SerializerInterface|\PHPUnit_Framework_MockObject_MockObject $serializerMock */ + $serializerMock = $this->getMockBuilder(SerializerInterface::class) + ->disableOriginalConstructor() + ->getMock(); $this->configType = new System( $this->source, $this->postProcessor, $this->fallback, $this->cache, - $this->serializer, + $serializerMock, $this->preProcessor, 1, 'system', - $this->reader + $this->reader, + $sensitiveFactory ); } @@ -133,49 +147,4 @@ public function testGetCachedWithLoadAllData() ->willReturn($data); $this->assertEquals($data, $this->configType->get('')); } - - public function testGetNotCached() - { - $path = 'stores/default/dev/unsecure/url'; - $url = 'http://magento.test/'; - - $dataToCache = [ - 'unsecure' => [ - 'url' => $url - ] - ]; - $data = [ - 'default' => [], - 'websites' => [], - 'stores' => [ - 'default' => [ - 'dev' => [ - 'unsecure' => [ - 'url' => $url - ] - ] - ] - ] - ]; - $this->cache->expects($this->any()) - ->method('load') - ->willReturnOnConsecutiveCalls(false, false); - - $this->serializer->expects($this->atLeastOnce()) - ->method('serialize') - ->willReturn(serialize($dataToCache)); - $this->cache->expects($this->atLeastOnce()) - ->method('save') - ->willReturnSelf(); - $this->reader->expects($this->once()) - ->method('read') - ->willReturn($data); - $this->postProcessor->expects($this->once()) - ->method('process') - ->with($data) - ->willReturn($data); - - $this->assertEquals($url, $this->configType->get($path)); - $this->assertEquals($url, $this->configType->get($path)); - } } diff --git a/app/code/Magento/Config/composer.json b/app/code/Magento/Config/composer.json index 1201c5942b558..e43c9b0382e25 100644 --- a/app/code/Magento/Config/composer.json +++ b/app/code/Magento/Config/composer.json @@ -13,7 +13,7 @@ "magento/module-deploy": "100.2.*" }, "type": "magento2-module", - "version": "101.0.5", + "version": "101.0.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/ConfigurableImportExport/Test/Mftf/composer.json b/app/code/Magento/ConfigurableImportExport/Test/Mftf/composer.json deleted file mode 100644 index 0ee8e8ad235c5..0000000000000 --- a/app/code/Magento/ConfigurableImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-configurable-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ConfigurableImportExport/composer.json b/app/code/Magento/ConfigurableImportExport/composer.json index 10e6637e7558a..b2070fe7ebc44 100644 --- a/app/code/Magento/ConfigurableImportExport/composer.json +++ b/app/code/Magento/ConfigurableImportExport/composer.json @@ -11,7 +11,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php index a6bec4ac6274f..c4cbed58f1587 100644 --- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php +++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Attribute.php @@ -18,7 +18,7 @@ class Attribute extends \Magento\Framework\Model\AbstractExtensibleModel implements \Magento\ConfigurableProduct\Api\Data\OptionInterface { - /**#@+ + /** * Constants for field names */ const KEY_ATTRIBUTE_ID = 'attribute_id'; @@ -27,9 +27,10 @@ class Attribute extends \Magento\Framework\Model\AbstractExtensibleModel impleme const KEY_IS_USE_DEFAULT = 'is_use_default'; const KEY_VALUES = 'values'; const KEY_PRODUCT_ID = 'product_id'; - /**#@-*/ - /**#@-*/ + /** + * @var MetadataPool|\Magento\Framework\EntityManager\MetadataPool + */ private $metadataPool; /** diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml new file mode 100644 index 0000000000000..75686d23a11b9 --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateApiConfigurableProductActionGroup"> + <arguments> + <argument name="productName" defaultValue="ApiConfigurableProductWithOutCategory" type="string"/> + </arguments> + + <!-- Create the configurable product based on the data in the /data folder --> + <createData entity="ApiConfigurableProductWithOutCategory" stepKey="createConfigProduct"> + <field key="name">{{productName}}</field> + </createData> + + <!-- Create attribute with 2 options to be used in children products --> + <createData entity="productAttributeWithDropdownTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="addAttributeToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + + <!-- Create the 2 children that will be a part of the configurable product --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + + <!-- Assign the two products to the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml index 83e0aa1deaedf..a2f824dd8864e 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="BaseConfigurableProduct" type="product"> <data key="sku" unique="suffix">configurable</data> <data key="type_id">configurable</data> @@ -38,4 +38,15 @@ <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> <requiredEntity type="custom_attribute_array">CustomAttributeCategoryIds</requiredEntity> </entity> + <entity name="ApiConfigurableProductWithOutCategory" type="product"> + <data key="sku" unique="suffix">api-configurable-product-with-out-category</data> + <data key="type_id">configurable</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">API Configurable Product</data> + <data key="urlKey" unique="suffix">api-configurable-product</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml index 21dcf998a6399..e98175bc8d40b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ConfigurableProductOptionData.xml @@ -7,11 +7,18 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ConfigurableProductTwoOptions" type="ConfigurableProductOption"> <var key="attribute_id" entityKey="attribute_id" entityType="ProductAttribute" /> <data key="label">option</data> <requiredEntity type="ValueIndex">ValueIndex1</requiredEntity> <requiredEntity type="ValueIndex">ValueIndex2</requiredEntity> </entity> + <entity name="ConfigurableProductThreeOptions" type="ConfigurableProductOption"> + <var key="attribute_id" entityKey="attribute_id" entityType="ProductAttribute" /> + <data key="label">option</data> + <requiredEntity type="ValueIndex">ValueIndex1</requiredEntity> + <requiredEntity type="ValueIndex">ValueIndex2</requiredEntity> + <requiredEntity type="ValueIndex">ValueIndex3</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml index 54d489a446fd7..e1cd70790a6b2 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ValueIndexData.xml @@ -7,11 +7,14 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="ValueIndex1" type="ValueIndex"> <var key="value_index" entityKey="value" entityType="ProductAttributeOption"/> </entity> <entity name="ValueIndex2" type="ValueIndex"> <var key="value_index" entityKey="value" entityType="ProductAttributeOption"/> </entity> + <entity name="ValueIndex3" type="ValueIndex"> + <var key="value_index" entityKey="value" entityType="ProductAttributeOption"/> + </entity> </entities> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml index 5dcbfbaf44037..a1e3e0b83e722 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductWithFileCustomOptionTest.xml @@ -12,7 +12,7 @@ <annotations> <features value="ConfigurableProduct"/> <stories value="Add configurable product to cart"/> - <title value="Correct error message and redirect with invalid file option"/> + <title value="Correct error message and redirect with invalid file custom option"/> <description value="Configurable product has file custom option. When adding to cart with an invalid filetype, the correct error message is shown, and options remain selected."/> <severity value="CRITICAL"/> <testCaseId value="MAGETWO-93318"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml new file mode 100644 index 0000000000000..c8ca2ced0d60f --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontSortingByPriceForConfigurableWithCatalogRuleAppliedTest.xml @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontSortingByPriceForConfigurableProductWithCatalogRuleAppliedTest"> + <annotations> + <features value="ConfigurableProduct"/> + <stories value="View soting by price in storefront"/> + <title value="Sorting by price for Configurable with Catalog Rule applied"/> + <description value="Sort by price should be correct if the apply Catalog Rule to child product of configurable product"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-76081"/> + <group value="configurable_product"/> + </annotations> + <before> + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">5.00</field> + </createData> + <createData entity="SimpleProduct" stepKey="createSimpleProduct2"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">10.00</field> + </createData> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="productAttributeWithDropdownTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="ProductAttributeOption3" stepKey="createConfigProductAttributeOption3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="3" stepKey="getConfigAttributeOption3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <field key="price">15.00</field> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + <field key="price">20.00</field> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct3"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption3"/> + <field key="price">25.00</field> + </createData> + <createData entity="ConfigurableProductThreeOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + <requiredEntity createDataKey="getConfigAttributeOption3"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild3"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct3"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!--SKU Product Attribute is enabled for Promo Rule Conditions--> + <actionGroup ref="navigateToEditProductAttribute" stepKey="navigateToSkuProductAttribute"> + <argument name="attributeLabel" value="sku"/> + </actionGroup> + <actionGroup ref="changeUseForPromoRuleConditionsProductAttribute" stepKey="changeUseForPromoRuleConditionsProductAttributeToYes"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createSimpleProduct2" stepKey="deleteSimpleProduct2"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigChildProduct3" stepKey="deleteConfigChildProduct3"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + + <!--SKU Product Attribute is disable for Promo Rule Conditions--> + <actionGroup ref="navigateToEditProductAttribute" stepKey="navigateToSkuProductAttribute"> + <argument name="attributeLabel" value="sku"/> + </actionGroup> + <actionGroup ref="changeUseForPromoRuleConditionsProductAttribute" stepKey="changeUseForPromoRuleConditionsProductAttributeToNo"> + <argument name="useForPromoRule" value="No"/> + </actionGroup> + <actionGroup ref="logout" stepKey="logoutFromAdmin"/> + </after> + + <!--Open category with products and Sort by price desc--> + <actionGroup ref="GoToStorefrontCategoryPageByParameters" stepKey="goToStorefrontCategoryPage"> + <argument name="category" value="$$createCategory.custom_attributes[url_key]$$"/> + <argument name="mode" value="grid"/> + <argument name="sortBy" value="price"/> + <argument name="sort" value="desc"/> + </actionGroup> + <see selector="{{StorefrontCategoryMainSection.categoryPageProductName('1')}}" userInput="$$createConfigProduct.name$$" stepKey="seeConfigurableProduct"/> + <see selector="{{StorefrontCategoryMainSection.categoryPageProductName('2')}}" userInput="$$createSimpleProduct2.name$$" stepKey="seeSimpleProductTwo"/> + <see selector="{{StorefrontCategoryMainSection.categoryPageProductName('3')}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct"/> + + <!--Create and apply catalog price rule--> + <actionGroup ref="newCatalogPriceRuleByUIWithConditionIsSKU" stepKey="createCatalogPriceRule"> + <argument name="catalogRule" value="CatalogRule96PercentDiscount" /> + <argument name="productSku" value="$$createConfigChildProduct3.sku$$" /> + </actionGroup> + <click selector="{{AdminCatalogPriceRuleGridSection.applyRulesButton}}" stepKey="clickApplyRules"/> + + <magentoCLI command="indexer:reindex" stepKey="reindex1"/> + <magentoCLI command="cache:flush" stepKey="flushCache1"/> + + <!--Reopen category with products and Sort by price desc--> + <actionGroup ref="GoToStorefrontCategoryPageByParameters" stepKey="goToStorefrontCategoryPage2"> + <argument name="category" value="$$createCategory.custom_attributes[url_key]$$"/> + <argument name="mode" value="grid"/> + <argument name="sortBy" value="price"/> + <argument name="sort" value="desc"/> + </actionGroup> + <see selector="{{StorefrontCategoryMainSection.categoryPageProductName('1')}}" userInput="$$createSimpleProduct2.name$$" stepKey="seeSimpleProductTwo2"/> + <see selector="{{StorefrontCategoryMainSection.categoryPageProductName('2')}}" userInput="$$createSimpleProduct.name$$" stepKey="seeSimpleProduct2"/> + <see selector="{{StorefrontCategoryMainSection.categoryPageProductName('3')}}" userInput="$$createConfigProduct.name$$" stepKey="seeConfigurableProduct2"/> + + <!-- Delete the rule --> + <actionGroup ref="RemoveCatalogPriceRule" stepKey="deletePriceRule"> + <argument name="ruleName" value="CatalogRule96PercentDiscount.name" /> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/composer.json b/app/code/Magento/ConfigurableProduct/Test/Mftf/composer.json deleted file mode 100644 index d8da8e39013a1..0000000000000 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "magento/functional-test-module-configurable-product", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-msrp": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-webapi": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-product-video": "100.0.0-dev", - "magento/functional-test-module-configurable-sample-data": "100.0.0-dev", - "magento/functional-test-module-product-links-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 4c583d79ce418..eae9dac046b23 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -19,12 +19,13 @@ "suggest": { "magento/module-webapi": "100.2.*", "magento/module-sales": "101.0.*", + "magento/module-sales-rule": "101.0.*", "magento/module-product-video": "100.2.*", "magento/module-configurable-sample-data": "Sample Data version:100.2.*", "magento/module-product-links-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index dfbad0dd5a764..1dbb0969687d5 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -228,4 +228,11 @@ </argument> </arguments> </type> + <type name="Magento\SalesRule\Model\Quote\ChildrenValidationLocator"> + <arguments> + <argument name="productTypeChildrenValidationMap" xsi:type="array"> + <item name="configurable" xsi:type="boolean">false</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index 8cabe71c17504..6b6c1762fadd9 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -360,7 +360,11 @@ define([ index = 1, allowedProducts, i, - j; + j, + basePrice = parseFloat(this.options.spConfig.prices.basePrice.amount), + optionFinalPrice, + optionPriceDiff, + optionPrices = this.options.spConfig.optionPrices; this._clearSelect(element); element.options[0] = new Option('', ''); @@ -374,6 +378,7 @@ define([ if (options) { for (i = 0; i < options.length; i++) { allowedProducts = []; + optionPriceDiff = 0; /* eslint-disable max-depth */ if (prevConfig) { @@ -387,6 +392,20 @@ define([ } } else { allowedProducts = options[i].products.slice(0); + + if (typeof allowedProducts[0] !== 'undefined' && + typeof optionPrices[allowedProducts[0]] !== 'undefined') { + + optionFinalPrice = parseFloat(optionPrices[allowedProducts[0]].finalPrice.amount); + optionPriceDiff = optionFinalPrice - basePrice; + + if (optionPriceDiff !== 0) { + options[i].label = options[i].label + ' ' + priceUtils.formatPrice( + optionPriceDiff, + this.options.priceFormat, + true); + } + } } if (allowedProducts.length > 0) { @@ -394,7 +413,7 @@ define([ element.options[index] = new Option(this._getOptionLabel(options[i]), options[i].id); if (typeof options[i].price !== 'undefined') { - element.options[index].setAttribute('price', options[i].prices); + element.options[index].setAttribute('price', options[i].price); } element.options[index].config = options[i]; diff --git a/app/code/Magento/ConfigurableProductSales/Test/Mftf/composer.json b/app/code/Magento/ConfigurableProductSales/Test/Mftf/composer.json deleted file mode 100644 index 21dbce122386f..0000000000000 --- a/app/code/Magento/ConfigurableProductSales/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-configurable-product-sales", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-configurable-product": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ConfigurableProductSales/composer.json b/app/code/Magento/ConfigurableProductSales/composer.json index d1fbc142b627f..ca6638924b8c6 100644 --- a/app/code/Magento/ConfigurableProductSales/composer.json +++ b/app/code/Magento/ConfigurableProductSales/composer.json @@ -12,7 +12,7 @@ "magento/module-configurable-product": "100.2.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Contact/Test/Mftf/composer.json b/app/code/Magento/Contact/Test/Mftf/composer.json deleted file mode 100644 index bdfb3265948ac..0000000000000 --- a/app/code/Magento/Contact/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-contact", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Contact/composer.json b/app/code/Magento/Contact/composer.json index 9ceca3bdfa80b..49ac8ec40dbc4 100644 --- a/app/code/Magento/Contact/composer.json +++ b/app/code/Magento/Contact/composer.json @@ -10,7 +10,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Cookie/Test/Mftf/composer.json b/app/code/Magento/Cookie/Test/Mftf/composer.json deleted file mode 100644 index d65a11f025000..0000000000000 --- a/app/code/Magento/Cookie/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-cookie", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-backend": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Cookie/composer.json b/app/code/Magento/Cookie/composer.json index 23bd37234f939..cc04b0ccd5d7d 100644 --- a/app/code/Magento/Cookie/composer.json +++ b/app/code/Magento/Cookie/composer.json @@ -10,7 +10,7 @@ "magento/module-backend": "100.2.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Cron/Model/Config/Backend/Product/Alert.php b/app/code/Magento/Cron/Model/Config/Backend/Product/Alert.php index 2fc0f0ab4c1a0..87618785adb1d 100644 --- a/app/code/Magento/Cron/Model/Config/Backend/Product/Alert.php +++ b/app/code/Magento/Cron/Model/Config/Backend/Product/Alert.php @@ -72,14 +72,14 @@ public function afterSave() $frequency = $this->getData('groups/productalert_cron/fields/frequency/value'); $cronExprArray = [ - intval($time[1]), //Minute - intval($time[0]), //Hour + (int)$time[1], //Minute + (int)$time[0], //Hour $frequency == \Magento\Cron\Model\Config\Source\Frequency::CRON_MONTHLY ? '1' : '*', //Day of the Month '*', //Month of the Year $frequency == \Magento\Cron\Model\Config\Source\Frequency::CRON_WEEKLY ? '1' : '*', //Day of the Week ]; - $cronExprString = join(' ', $cronExprArray); + $cronExprString = implode(' ', $cronExprArray); try { $this->_configValueFactory->create()->load( diff --git a/app/code/Magento/Cron/Model/Config/Backend/Sitemap.php b/app/code/Magento/Cron/Model/Config/Backend/Sitemap.php index 681129916647d..31d8ba59ee42d 100644 --- a/app/code/Magento/Cron/Model/Config/Backend/Sitemap.php +++ b/app/code/Magento/Cron/Model/Config/Backend/Sitemap.php @@ -70,8 +70,8 @@ public function afterSave() $frequency = $this->getData('groups/generate/fields/frequency/value'); $cronExprArray = [ - intval($time[1]), //Minute - intval($time[0]), //Hour + (int)$time[1], //Minute + (int)$time[0], //Hour $frequency == \Magento\Cron\Model\Config\Source\Frequency::CRON_MONTHLY ? '1' : '*', //Day of the Month '*', //Month of the Year $frequency == \Magento\Cron\Model\Config\Source\Frequency::CRON_WEEKLY ? '1' : '*', //# Day of the Week diff --git a/app/code/Magento/Cron/Test/Mftf/composer.json b/app/code/Magento/Cron/Test/Mftf/composer.json deleted file mode 100644 index cdee4efef8a24..0000000000000 --- a/app/code/Magento/Cron/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-cron", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Cron/Test/Unit/Model/System/Config/Initial/ConverterTest.php b/app/code/Magento/Cron/Test/Unit/Model/System/Config/Initial/ConverterTest.php new file mode 100644 index 0000000000000..703926b4c0116 --- /dev/null +++ b/app/code/Magento/Cron/Test/Unit/Model/System/Config/Initial/ConverterTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Cron\Test\Unit\Model\System\Config\Initial; + +use Magento\Cron\Model\Groups\Config\Data as GroupsConfigModel; +use Magento\Cron\Model\System\Config\Initial\Converter as ConverterPlugin; +use Magento\Framework\App\Config\Initial\Converter; + +/** + * Class ConverterTest + * + * Unit test for \Magento\Cron\Model\System\Config\Initial\Converter + */ +class ConverterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var GroupsConfigModel|\PHPUnit_Framework_MockObject_MockObject + */ + private $groupsConfigMock; + + /** + * @var Converter|\PHPUnit_Framework_MockObject_MockObject + */ + private $converterMock; + + /** + * @var ConverterPlugin + */ + private $converterPlugin; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->groupsConfigMock = $this->getMockBuilder( + GroupsConfigModel::class + )->disableOriginalConstructor()->getMock(); + $this->converterMock = $this->getMockBuilder(Converter::class)->getMock(); + $this->converterPlugin = new ConverterPlugin($this->groupsConfigMock); + } + + /** + * Tests afterConvert method with no $result['data']['default']['system'] set + */ + public function testAfterConvertWithNoData() + { + $expectedResult = ['test']; + $this->groupsConfigMock->expects($this->never()) + ->method('get'); + + $result = $this->converterPlugin->afterConvert($this->converterMock, $expectedResult); + + self::assertSame($expectedResult, $result); + } + + /** + * Tests afterConvert method with $result['data']['default']['system'] set + */ + public function testAfterConvertWithData() + { + $groups = [ + 'group1' => ['val1' => ['value' => '1']], + 'group2' => ['val2' => ['value' => '2']] + ]; + $expectedResult['data']['default']['system']['cron'] = [ + 'group1' => [ + 'val1' => '1' + ], + 'group2' => [ + 'val2' => '2' + ] + ]; + $result['data']['default']['system']['cron'] = '1'; + + $this->groupsConfigMock->expects($this->once()) + ->method('get') + ->willReturn($groups); + + $result = $this->converterPlugin->afterConvert($this->converterMock, $result); + + self::assertEquals($expectedResult, $result); + } +} diff --git a/app/code/Magento/Cron/composer.json b/app/code/Magento/Cron/composer.json index 5faf1d15e7782..a8c124dc57207 100644 --- a/app/code/Magento/Cron/composer.json +++ b/app/code/Magento/Cron/composer.json @@ -10,7 +10,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Cron/etc/di.xml b/app/code/Magento/Cron/etc/di.xml index a37f3760b70a5..3e3bdc2053576 100644 --- a/app/code/Magento/Cron/etc/di.xml +++ b/app/code/Magento/Cron/etc/di.xml @@ -16,6 +16,18 @@ <type name="Magento\Framework\App\Config\Initial\Converter"> <plugin name="cron_system_config_initial_converter_plugin" type="Magento\Cron\Model\System\Config\Initial\Converter" /> </type> + <virtualType name="Magento\Cron\Model\VirtualLoggerHandler" type="Magento\Framework\Logger\Handler\Base"> + <arguments> + <argument name="fileName" xsi:type="string">/var/log/cron.log</argument> + </arguments> + </virtualType> + <virtualType name="Magento\Cron\Model\VirtualLogger" type="Magento\Framework\Logger\Monolog"> + <arguments> + <argument name="handlers" xsi:type="array"> + <item name="system" xsi:type="object">Magento\Cron\Model\VirtualLoggerHandler</item> + </argument> + </arguments> + </virtualType> <!-- @api --> <virtualType name="shellBackground" type="Magento\Framework\Shell"> <arguments> @@ -25,6 +37,7 @@ <type name="Magento\Cron\Observer\ProcessCronQueueObserver"> <arguments> <argument name="shell" xsi:type="object">shellBackground</argument> + <argument name="logger" xsi:type="object">Magento\Cron\Model\VirtualLogger</argument> </arguments> </type> <type name="Magento\Framework\Console\CommandListInterface"> diff --git a/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php b/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php index fcde688a1e145..518e7fcf4181f 100644 --- a/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php +++ b/app/code/Magento/CurrencySymbol/Model/System/Currencysymbol.php @@ -192,9 +192,11 @@ public function getCurrencySymbolsData() */ public function setCurrencySymbolsData($symbols = []) { - foreach ($this->getCurrencySymbolsData() as $code => $values) { - if (isset($symbols[$code]) && ($symbols[$code] == $values['parentSymbol'] || empty($symbols[$code]))) { - unset($symbols[$code]); + if (!$this->_storeManager->isSingleStoreMode()) { + foreach ($this->getCurrencySymbolsData() as $code => $values) { + if (isset($symbols[$code]) && ($symbols[$code] == $values['parentSymbol'] || empty($symbols[$code]))) { + unset($symbols[$code]); + } } } $value = []; diff --git a/app/code/Magento/CurrencySymbol/Test/Mftf/composer.json b/app/code/Magento/CurrencySymbol/Test/Mftf/composer.json deleted file mode 100644 index e6815bb61a2d3..0000000000000 --- a/app/code/Magento/CurrencySymbol/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-currency-symbol", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CurrencySymbol/composer.json b/app/code/Magento/CurrencySymbol/composer.json index 3afa8c6338378..da76425436038 100644 --- a/app/code/Magento/CurrencySymbol/composer.json +++ b/app/code/Magento/CurrencySymbol/composer.json @@ -11,7 +11,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml index 0ba3c7ed2d7d6..6e9b9a396ec2f 100644 --- a/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml +++ b/app/code/Magento/CurrencySymbol/view/adminhtml/templates/grid.phtml @@ -23,10 +23,9 @@ </label> <div class="admin__field-control"> <input id="custom_currency_symbol<?= /* @escapeNotVerified */ $code ?>" - class="required-entry admin__control-text" + class="required-entry admin__control-text <?= $data['inherited'] ? 'disabled' : '' ?>" type="text" value="<?= $block->escapeHtmlAttr($data['displaySymbol']) ?>" - <?= $data['inherited'] ? ' disabled="disabled"' : '' ?> name="custom_currency_symbol[<?= /* @escapeNotVerified */ $code ?>]"> <div class="admin__field admin__field-option"> <input id="custom_currency_symbol_inherit<?= /* @escapeNotVerified */ $code ?>" @@ -49,16 +48,18 @@ require(['jquery', "mage/mage", 'prototype'], function(jQuery){ function toggleUseDefault(code, value) { - checkbox = $('custom_currency_symbol_inherit'+code); - input = $('custom_currency_symbol'+code); - if (checkbox.checked) { - input.value = value; - input.disabled = true; + checkbox = jQuery('#custom_currency_symbol_inherit'+code); + input = jQuery('#custom_currency_symbol'+code); + + if (checkbox.is(':checked')) { + input.addClass('disabled'); + input.val(value); + input.prop('readonly', true); } else { - input.disabled = false; + input.removeClass('disabled'); + input.prop('readonly', false); } } - window.toggleUseDefault = toggleUseDefault; }); </script> diff --git a/app/code/Magento/Customer/Api/AccountManagementInterface.php b/app/code/Magento/Customer/Api/AccountManagementInterface.php index d2f9fb7ebc420..0bda1fc4bb815 100644 --- a/app/code/Magento/Customer/Api/AccountManagementInterface.php +++ b/app/code/Magento/Customer/Api/AccountManagementInterface.php @@ -7,6 +7,8 @@ namespace Magento\Customer\Api; +use Magento\Framework\Exception\InputException; + /** * Interface for managing customers accounts. * @api @@ -144,19 +146,24 @@ public function initiatePasswordReset($email, $template, $websiteId = null); /** * Reset customer password. * - * @param string $email + * @param string $email If empty value given then the customer + * will be matched by the RP token. * @param string $resetToken * @param string $newPassword + * * @return bool true on success * @throws \Magento\Framework\Exception\LocalizedException + * @throws InputException */ public function resetPassword($email, $resetToken, $newPassword); /** * Check if password reset token is valid. * - * @param int $customerId + * @param int $customerId If 0 is given then a customer + * will be matched by the RP token. * @param string $resetPasswordLinkToken + * * @return bool True if the token is valid * @throws \Magento\Framework\Exception\State\InputMismatchException If token is mismatched * @throws \Magento\Framework\Exception\State\ExpiredException If token is expired diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php index 81b7b8b3f96b5..c7023d0404f75 100644 --- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php +++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfo.php @@ -461,7 +461,7 @@ protected function getOnlineMinutesInterval() 'customer/online_customers/online_minutes_interval', \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); - return intval($configValue) > 0 ? intval($configValue) : self::DEFAULT_ONLINE_MINUTES_INTERVAL; + return (int)$configValue > 0 ? (int)$configValue : self::DEFAULT_ONLINE_MINUTES_INTERVAL; } /** diff --git a/app/code/Magento/Customer/Controller/Account/CreatePassword.php b/app/code/Magento/Customer/Controller/Account/CreatePassword.php index fb2e3dd42908b..a86f42c14a027 100644 --- a/app/code/Magento/Customer/Controller/Account/CreatePassword.php +++ b/app/code/Magento/Customer/Controller/Account/CreatePassword.php @@ -54,27 +54,30 @@ public function __construct( public function execute() { $resetPasswordToken = (string)$this->getRequest()->getParam('token'); - $customerId = (int)$this->getRequest()->getParam('id'); - $isDirectLink = $resetPasswordToken != '' && $customerId != 0; + $isDirectLink = $resetPasswordToken != ''; if (!$isDirectLink) { $resetPasswordToken = (string)$this->session->getRpToken(); - $customerId = (int)$this->session->getRpCustomerId(); } try { - $this->accountManagement->validateResetPasswordLinkToken($customerId, $resetPasswordToken); + $this->accountManagement->validateResetPasswordLinkToken( + 0, + $resetPasswordToken + ); if ($isDirectLink) { $this->session->setRpToken($resetPasswordToken); - $this->session->setRpCustomerId($customerId); $resultRedirect = $this->resultRedirectFactory->create(); $resultRedirect->setPath('*/*/createpassword'); + return $resultRedirect; } else { /** @var \Magento\Framework\View\Result\Page $resultPage */ $resultPage = $this->resultPageFactory->create(); - $resultPage->getLayout()->getBlock('resetPassword')->setCustomerId($customerId) + $resultPage->getLayout() + ->getBlock('resetPassword') ->setResetPasswordLinkToken($resetPasswordToken); + return $resultPage; } } catch (\Exception $exception) { diff --git a/app/code/Magento/Customer/Controller/Account/EditPost.php b/app/code/Magento/Customer/Controller/Account/EditPost.php index 3f895ad2f17ac..da0ad29c5c72f 100644 --- a/app/code/Magento/Customer/Controller/Account/EditPost.php +++ b/app/code/Magento/Customer/Controller/Account/EditPost.php @@ -20,6 +20,7 @@ use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\InvalidEmailOrPasswordException; use Magento\Framework\Exception\State\UserLockedException; +use Magento\Framework\Escaper; /** * Class EditPost @@ -72,6 +73,9 @@ class EditPost extends \Magento\Customer\Controller\AbstractAccount */ private $customerMapper; + /** @var Escaper */ + private $escaper; + /** * @param Context $context * @param Session $customerSession @@ -79,6 +83,7 @@ class EditPost extends \Magento\Customer\Controller\AbstractAccount * @param CustomerRepositoryInterface $customerRepository * @param Validator $formKeyValidator * @param CustomerExtractor $customerExtractor + * @param Escaper|null $escaper */ public function __construct( Context $context, @@ -86,7 +91,8 @@ public function __construct( AccountManagementInterface $customerAccountManagement, CustomerRepositoryInterface $customerRepository, Validator $formKeyValidator, - CustomerExtractor $customerExtractor + CustomerExtractor $customerExtractor, + Escaper $escaper = null ) { parent::__construct($context); $this->session = $customerSession; @@ -94,6 +100,7 @@ public function __construct( $this->customerRepository = $customerRepository; $this->formKeyValidator = $formKeyValidator; $this->customerExtractor = $customerExtractor; + $this->escaper = $escaper ?: ObjectManager::getInstance()->get(Escaper::class); } /** @@ -175,9 +182,9 @@ public function execute() $this->messageManager->addError($message); return $resultRedirect->setPath('customer/account/login'); } catch (InputException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($e->getMessage())); foreach ($e->getErrors() as $error) { - $this->messageManager->addError($error->getMessage()); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($error->getMessage())); } } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->messageManager->addError($e->getMessage()); diff --git a/app/code/Magento/Customer/Controller/Account/ResetPasswordPost.php b/app/code/Magento/Customer/Controller/Account/ResetPasswordPost.php index 3de44e35d2447..ab6944a995d84 100644 --- a/app/code/Magento/Customer/Controller/Account/ResetPasswordPost.php +++ b/app/code/Magento/Customer/Controller/Account/ResetPasswordPost.php @@ -12,7 +12,6 @@ use Magento\Framework\App\Action\Context; use Magento\Framework\Exception\InputException; use Magento\Customer\Model\Customer\CredentialsValidator; -use Magento\Framework\App\ObjectManager; class ResetPasswordPost extends \Magento\Customer\Controller\AbstractAccount { @@ -31,17 +30,14 @@ class ResetPasswordPost extends \Magento\Customer\Controller\AbstractAccount */ protected $session; - /** - * @var CredentialsValidator - */ - private $credentialsValidator; - /** * @param Context $context * @param Session $customerSession * @param AccountManagementInterface $accountManagement * @param CustomerRepositoryInterface $customerRepository * @param CredentialsValidator|null $credentialsValidator + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( Context $context, @@ -53,8 +49,6 @@ public function __construct( $this->session = $customerSession; $this->accountManagement = $accountManagement; $this->customerRepository = $customerRepository; - $this->credentialsValidator = $credentialsValidator ?: ObjectManager::getInstance() - ->get(CredentialsValidator::class); parent::__construct($context); } @@ -70,27 +64,33 @@ public function execute() /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultRedirectFactory->create(); $resetPasswordToken = (string)$this->getRequest()->getQuery('token'); - $customerId = (int)$this->getRequest()->getQuery('id'); $password = (string)$this->getRequest()->getPost('password'); $passwordConfirmation = (string)$this->getRequest()->getPost('password_confirmation'); if ($password !== $passwordConfirmation) { $this->messageManager->addError(__("New Password and Confirm New Password values didn't match.")); - $resultRedirect->setPath('*/*/createPassword', ['id' => $customerId, 'token' => $resetPasswordToken]); + $resultRedirect->setPath( + '*/*/createPassword', + ['token' => $resetPasswordToken] + ); return $resultRedirect; } if (iconv_strlen($password) <= 0) { $this->messageManager->addError(__('Please enter a new password.')); - $resultRedirect->setPath('*/*/createPassword', ['id' => $customerId, 'token' => $resetPasswordToken]); + $resultRedirect->setPath( + '*/*/createPassword', + ['token' => $resetPasswordToken] + ); return $resultRedirect; } try { - $customerEmail = $this->customerRepository->getById($customerId)->getEmail(); - $this->credentialsValidator->checkPasswordDifferentFromEmail($customerEmail, $password); - $this->accountManagement->resetPassword($customerEmail, $resetPasswordToken, $password); + $this->accountManagement->resetPassword( + '', + $resetPasswordToken, + $password + ); $this->session->unsRpToken(); - $this->session->unsRpCustomerId(); $this->messageManager->addSuccess(__('You updated your password.')); $resultRedirect->setPath('*/*/login'); return $resultRedirect; @@ -102,7 +102,11 @@ public function execute() } catch (\Exception $exception) { $this->messageManager->addError(__('Something went wrong while saving the new password.')); } - $resultRedirect->setPath('*/*/createPassword', ['id' => $customerId, 'token' => $resetPasswordToken]); + + $resultRedirect->setPath( + '*/*/createPassword', + ['token' => $resetPasswordToken] + ); return $resultRedirect; } } diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/AbstractMassAction.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/AbstractMassAction.php index e26b49aaebe7a..6b80cd5b3a6a5 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/AbstractMassAction.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/AbstractMassAction.php @@ -10,6 +10,7 @@ use Magento\Framework\App\ResponseInterface; use Magento\Framework\Controller\ResultInterface; use Magento\Backend\App\Action\Context; +use Magento\Framework\Exception\NotFoundException; use Magento\Ui\Component\MassAction\Filter; use Magento\Customer\Model\ResourceModel\Customer\CollectionFactory; @@ -60,6 +61,10 @@ public function __construct(Context $context, Filter $filter, CollectionFactory */ public function execute() { + if (!$this->getRequest()->isPost()) { + throw new NotFoundException(__('Page not found')); + } + try { $collection = $this->filter->getCollection($this->collectionFactory->create()); return $this->massAction($collection); diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php index 2d0ee3ae13da4..6753a48d02d6a 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/InlineEdit.php @@ -6,13 +6,15 @@ namespace Magento\Customer\Controller\Adminhtml\Index; use Magento\Backend\App\Action; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; use Magento\Customer\Model\EmailNotificationInterface; -use Magento\Customer\Test\Block\Form\Login; use Magento\Customer\Ui\Component\Listing\AttributeRepository; -use Magento\Customer\Api\Data\CustomerInterface; -use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Message\MessageInterface; /** + * Customer inline edit action + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class InlineEdit extends \Magento\Backend\App\Action @@ -101,7 +103,11 @@ private function getEmailNotification() } /** + * Inline edit action execute + * * @return \Magento\Framework\Controller\Result\Json + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function execute() { @@ -249,7 +255,7 @@ protected function processAddressData(array $data) protected function getErrorMessages() { $messages = []; - foreach ($this->getMessageManager()->getMessages()->getItems() as $error) { + foreach ($this->getMessageManager()->getMessages()->getErrors() as $error) { $messages[] = $error->getText(); } return $messages; @@ -262,7 +268,7 @@ protected function getErrorMessages() */ protected function isErrorExists() { - return (bool)$this->getMessageManager()->getMessages(true)->getCount(); + return (bool)$this->getMessageManager()->getMessages(true)->getCountByType(MessageInterface::TYPE_ERROR); } /** diff --git a/app/code/Magento/Customer/Controller/Section/Load.php b/app/code/Magento/Customer/Controller/Section/Load.php index 7a2345c91750c..e37461d20f5de 100644 --- a/app/code/Magento/Customer/Controller/Section/Load.php +++ b/app/code/Magento/Customer/Controller/Section/Load.php @@ -70,11 +70,11 @@ public function execute() $sectionNames = $this->getRequest()->getParam('sections'); $sectionNames = $sectionNames ? array_unique(\explode(',', $sectionNames)) : null; - $updateSectionId = $this->getRequest()->getParam('update_section_id'); - if ('false' === $updateSectionId) { - $updateSectionId = false; + $forceNewSectionTimestamp = $this->getRequest()->getParam('force_new_section_timestamp'); + if ('false' === $forceNewSectionTimestamp) { + $forceNewSectionTimestamp = false; } - $response = $this->sectionPool->getSectionsData($sectionNames, (bool)$updateSectionId); + $response = $this->sectionPool->getSectionsData($sectionNames, (bool)$forceNewSectionTimestamp); } catch (\Exception $e) { $resultJson->setStatusHeader( \Zend\Http\Response::STATUS_CODE_400, diff --git a/app/code/Magento/Customer/CustomerData/Section/Identifier.php b/app/code/Magento/Customer/CustomerData/Section/Identifier.php index 2a770925d1c37..54d7cee2d90bd 100644 --- a/app/code/Magento/Customer/CustomerData/Section/Identifier.php +++ b/app/code/Magento/Customer/CustomerData/Section/Identifier.php @@ -43,12 +43,12 @@ public function __construct( /** * Init mark(identifier) for sections * - * @param bool $forceUpdate + * @param bool $forceNewTimestamp * @return int */ - public function initMark($forceUpdate) + public function initMark($forceNewTimestamp) { - if ($forceUpdate) { + if ($forceNewTimestamp) { $this->markId = time(); return $this->markId; } @@ -68,18 +68,18 @@ public function initMark($forceUpdate) * * @param array $sectionsData * @param null $sectionNames - * @param bool $updateIds + * @param bool $forceNewTimestamp * @return array */ - public function markSections(array $sectionsData, $sectionNames = null, $updateIds = false) + public function markSections(array $sectionsData, $sectionNames = null, $forceNewTimestamp = false) { if (!$sectionNames) { $sectionNames = array_keys($sectionsData); } - $markId = $this->initMark($updateIds); + $markId = $this->initMark($forceNewTimestamp); foreach ($sectionNames as $name) { - if ($updateIds || !array_key_exists(self::SECTION_KEY, $sectionsData[$name])) { + if ($forceNewTimestamp || !array_key_exists(self::SECTION_KEY, $sectionsData[$name])) { $sectionsData[$name][self::SECTION_KEY] = $markId; } } diff --git a/app/code/Magento/Customer/CustomerData/SectionPool.php b/app/code/Magento/Customer/CustomerData/SectionPool.php index 26e9140c63df5..be5ea09c0db33 100644 --- a/app/code/Magento/Customer/CustomerData/SectionPool.php +++ b/app/code/Magento/Customer/CustomerData/SectionPool.php @@ -55,10 +55,10 @@ public function __construct( /** * {@inheritdoc} */ - public function getSectionsData(array $sectionNames = null, $updateIds = false) + public function getSectionsData(array $sectionNames = null, $forceNewTimestamp = false) { $sectionsData = $sectionNames ? $this->getSectionDataByNames($sectionNames) : $this->getAllSectionData(); - $sectionsData = $this->identifier->markSections($sectionsData, $sectionNames, $updateIds); + $sectionsData = $this->identifier->markSections($sectionsData, $sectionNames, $forceNewTimestamp); return $sectionsData; } diff --git a/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php b/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php index c308804fd0f8d..ad73b9722b133 100644 --- a/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php +++ b/app/code/Magento/Customer/CustomerData/SectionPoolInterface.php @@ -14,8 +14,8 @@ interface SectionPoolInterface * Get section data by section names. If $sectionNames is null then return all sections data * * @param array $sectionNames - * @param bool $updateIds + * @param bool $forceNewTimestamp * @return array */ - public function getSectionsData(array $sectionNames = null, $updateIds = false); + public function getSectionsData(array $sectionNames = null, $forceNewTimestamp = false); } diff --git a/app/code/Magento/Customer/Model/AccountManagement.php b/app/code/Magento/Customer/Model/AccountManagement.php index 35684241faac1..8f2565189ef4c 100644 --- a/app/code/Magento/Customer/Model/AccountManagement.php +++ b/app/code/Magento/Customer/Model/AccountManagement.php @@ -19,6 +19,7 @@ use Magento\Customer\Model\Metadata\Validator; use Magento\Eav\Model\Validator\Attribute\Backend; use Magento\Framework\Api\ExtensibleDataObjectConverter; +use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\App\Area; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; @@ -40,6 +41,7 @@ use Magento\Framework\Intl\DateTimeFactory; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Math\Random; +use Magento\Framework\Phrase; use Magento\Framework\Reflection\DataObjectProcessor; use Magento\Framework\Registry; use Magento\Framework\Stdlib\DateTime; @@ -325,6 +327,11 @@ class AccountManagement implements AccountManagementInterface */ private $accountConfirmation; + /** + * @var SearchCriteriaBuilder + */ + private $searchCriteriaBuilder; + /** * @param CustomerFactory $customerFactory * @param ManagerInterface $eventManager @@ -356,6 +363,7 @@ class AccountManagement implements AccountManagementInterface * @param SessionManagerInterface|null $sessionManager * @param SaveHandlerInterface|null $saveHandler * @param CollectionFactory|null $visitorCollectionFactory + * @param SearchCriteriaBuilder|null $searchCriteriaBuilder * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -387,7 +395,8 @@ public function __construct( AccountConfirmation $accountConfirmation = null, SessionManagerInterface $sessionManager = null, SaveHandlerInterface $saveHandler = null, - CollectionFactory $visitorCollectionFactory = null + CollectionFactory $visitorCollectionFactory = null, + SearchCriteriaBuilder $searchCriteriaBuilder = null ) { $this->customerFactory = $customerFactory; $this->eventManager = $eventManager; @@ -423,6 +432,8 @@ public function __construct( ?: ObjectManager::getInstance()->get(SaveHandlerInterface::class); $this->visitorCollectionFactory = $visitorCollectionFactory ?: ObjectManager::getInstance()->get(CollectionFactory::class); + $this->searchCriteriaBuilder = $searchCriteriaBuilder + ?: ObjectManager::getInstance()->get(SearchCriteriaBuilder::class); } /** @@ -596,14 +607,62 @@ public function initiatePasswordReset($email, $template, $websiteId = null) return false; } + /** + * Match a customer by their RP token. + * + * @param string $rpToken + * @throws ExpiredException + * @throws NoSuchEntityException + * + * @return CustomerInterface + */ + private function matchCustomerByRpToken(string $rpToken): CustomerInterface + { + + $this->searchCriteriaBuilder->addFilter( + 'rp_token', + $rpToken + ); + $this->searchCriteriaBuilder->setPageSize(1); + $found = $this->customerRepository->getList( + $this->searchCriteriaBuilder->create() + ); + + if ($found->getTotalCount() > 1) { + //Failed to generated unique RP token + throw new ExpiredException( + new Phrase('Reset password token expired.') + ); + } + if ($found->getTotalCount() === 0) { + //Customer with such token not found. + throw NoSuchEntityException::singleField( + 'rp_token', + $rpToken + ); + } + + //Unique customer found. + return $found->getItems()[0]; + } + /** * {@inheritdoc} */ public function resetPassword($email, $resetToken, $newPassword) { - $customer = $this->customerRepository->get($email); + if (!$email) { + $customer = $this->matchCustomerByRpToken($resetToken); + $email = $customer->getEmail(); + } else { + $customer = $this->customerRepository->get($email); + } //Validate Token and new password strength $this->validateResetPasswordToken($customer->getId(), $resetToken); + $this->credentialsValidator->checkPasswordDifferentFromEmail( + $email, + $newPassword + ); $this->checkPasswordStrength($newPassword); //Update secure data $customerSecure = $this->customerRegistry->retrieveSecureData($customer->getId()); @@ -1012,12 +1071,9 @@ public function isCustomerInStore($customerWebsiteId, $storeId) private function validateResetPasswordToken($customerId, $resetPasswordLinkToken) { if (empty($customerId) || $customerId < 0) { - throw new InputException( - __( - 'Invalid value of "%value" provided for the %fieldName field.', - ['value' => $customerId, 'fieldName' => 'customerId'] - ) - ); + //Looking for the customer. + $customerId = $this->matchCustomerByRpToken($resetPasswordLinkToken) + ->getId(); } if (!is_string($resetPasswordLinkToken) || empty($resetPasswordLinkToken)) { $params = ['fieldName' => 'resetPasswordLinkToken']; diff --git a/app/code/Magento/Customer/Model/Address.php b/app/code/Magento/Customer/Model/Address.php index 1dcd8516af69f..c39420542248e 100644 --- a/app/code/Magento/Customer/Model/Address.php +++ b/app/code/Magento/Customer/Model/Address.php @@ -154,9 +154,6 @@ public function updateData(AddressInterface $address) // Need to explicitly set this due to discrepancy in the keys between model and data object $this->setIsDefaultBilling($address->isDefaultBilling()); $this->setIsDefaultShipping($address->isDefaultShipping()); - if (!$this->getAttributeSetId()) { - $this->setAttributeSetId(AddressMetadataInterface::ATTRIBUTE_SET_ID_ADDRESS); - } $customAttributes = $address->getCustomAttributes(); if ($customAttributes !== null) { foreach ($customAttributes as $attribute) { diff --git a/app/code/Magento/Customer/Model/Customer.php b/app/code/Magento/Customer/Model/Customer.php index e0a7281776de9..e359b904347ef 100644 --- a/app/code/Magento/Customer/Model/Customer.php +++ b/app/code/Magento/Customer/Model/Customer.php @@ -358,13 +358,6 @@ public function updateData($customer) $this->setId($customerId); } - // Need to use attribute set or future updates can cause data loss - if (!$this->getAttributeSetId()) { - $this->setAttributeSetId( - CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER - ); - } - return $this; } @@ -960,6 +953,16 @@ public function getSharedWebsiteIds() return $ids; } + /** + * Retrieve attribute set id for customer. + * + * @return int + */ + public function getAttributeSetId() + { + return parent::getAttributeSetId() ?: CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER; + } + /** * Set store to customer * diff --git a/app/code/Magento/Customer/Model/GroupManagement.php b/app/code/Magento/Customer/Model/GroupManagement.php index 47d7d7ad1ac41..48cb5d55061c5 100644 --- a/app/code/Magento/Customer/Model/GroupManagement.php +++ b/app/code/Magento/Customer/Model/GroupManagement.php @@ -8,16 +8,19 @@ namespace Magento\Customer\Model; use Magento\Customer\Api\Data\GroupInterface; +use Magento\Customer\Api\Data\GroupInterfaceFactory; +use Magento\Customer\Api\GroupRepositoryInterface; +use Magento\Framework\Api\FilterBuilder; use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SortOrderBuilder; use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Store\Model\StoreManagerInterface; -use Magento\Customer\Api\GroupRepositoryInterface; -use Magento\Customer\Api\Data\GroupInterfaceFactory; -use Magento\Customer\Model\GroupFactory; /** + * The class contains methods for getting information about a customer group + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class GroupManagement implements \Magento\Customer\Api\GroupManagementInterface @@ -65,6 +68,11 @@ class GroupManagement implements \Magento\Customer\Api\GroupManagementInterface */ protected $filterBuilder; + /** + * @var SortOrderBuilder + */ + private $sortOrderBuilder; + /** * @param StoreManagerInterface $storeManager * @param ScopeConfigInterface $scopeConfig @@ -73,6 +81,7 @@ class GroupManagement implements \Magento\Customer\Api\GroupManagementInterface * @param GroupInterfaceFactory $groupDataFactory * @param SearchCriteriaBuilder $searchCriteriaBuilder * @param FilterBuilder $filterBuilder + * @param SortOrderBuilder $sortOrderBuilder */ public function __construct( StoreManagerInterface $storeManager, @@ -81,7 +90,8 @@ public function __construct( GroupRepositoryInterface $groupRepository, GroupInterfaceFactory $groupDataFactory, SearchCriteriaBuilder $searchCriteriaBuilder, - FilterBuilder $filterBuilder + FilterBuilder $filterBuilder, + SortOrderBuilder $sortOrderBuilder = null ) { $this->storeManager = $storeManager; $this->scopeConfig = $scopeConfig; @@ -90,10 +100,12 @@ public function __construct( $this->groupDataFactory = $groupDataFactory; $this->searchCriteriaBuilder = $searchCriteriaBuilder; $this->filterBuilder = $filterBuilder; + $this->sortOrderBuilder = $sortOrderBuilder ?: ObjectManager::getInstance() + ->get(SortOrderBuilder::class); } /** - * {@inheritdoc} + * @inheritdoc */ public function isReadonly($groupId) { @@ -107,7 +119,7 @@ public function isReadonly($groupId) } /** - * {@inheritdoc} + * @inheritdoc */ public function getDefaultGroup($storeId = null) { @@ -133,7 +145,7 @@ public function getDefaultGroup($storeId = null) } /** - * {@inheritdoc} + * @inheritdoc */ public function getNotLoggedInGroup() { @@ -141,7 +153,7 @@ public function getNotLoggedInGroup() } /** - * {@inheritdoc} + * @inheritdoc */ public function getLoggedInGroups() { @@ -155,15 +167,20 @@ public function getLoggedInGroups() ->setConditionType('neq') ->setValue(self::CUST_GROUP_ALL) ->create(); + $groupNameSortOrder = $this->sortOrderBuilder + ->setField('customer_group_code') + ->setAscendingDirection() + ->create(); $searchCriteria = $this->searchCriteriaBuilder ->addFilters($notLoggedInFilter) ->addFilters($groupAll) + ->addSortOrder($groupNameSortOrder) ->create(); return $this->groupRepository->getList($searchCriteria)->getItems(); } /** - * {@inheritdoc} + * @inheritdoc */ public function getAllCustomersGroup() { diff --git a/app/code/Magento/Customer/Model/Renderer/Region.php b/app/code/Magento/Customer/Model/Renderer/Region.php index 5c7fcd38d6c52..ad620e4e4b3f2 100644 --- a/app/code/Magento/Customer/Model/Renderer/Region.php +++ b/app/code/Magento/Customer/Model/Renderer/Region.php @@ -80,7 +80,7 @@ public function render(AbstractElement $element) $regionCollection = self::$_regionCollections[$countryId]; } - $regionId = intval($element->getForm()->getElement('region_id')->getValue()); + $regionId = (int)$element->getForm()->getElement('region_id')->getValue(); $htmlAttributes = $element->getHtmlAttributes(); foreach ($htmlAttributes as $key => $attribute) { diff --git a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php index 12d9dc9bcaa22..29f97d9f643a7 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php +++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php @@ -205,12 +205,6 @@ public function save(\Magento\Customer\Api\Data\CustomerInterface $customer, $pa $customerModel->setStoreId($this->storeManager->getStore()->getId()); } - // Need to use attribute set or future updates can cause data loss - if (!$customerModel->getAttributeSetId()) { - $customerModel->setAttributeSetId( - \Magento\Customer\Api\CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER - ); - } $this->populateCustomerWithSecureData($customerModel, $passwordHash); // If customer email was changed, reset RpToken info diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php index a0530389f902a..d144b7f6b70ec 100644 --- a/app/code/Magento/Customer/Model/Visitor.php +++ b/app/code/Magento/Customer/Model/Visitor.php @@ -320,11 +320,9 @@ public function clean() */ public function getOnlineInterval() { - $configValue = intval( - $this->scopeConfig->getValue( - static::XML_PATH_ONLINE_INTERVAL, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE - ) + $configValue = (int)$this->scopeConfig->getValue( + static::XML_PATH_ONLINE_INTERVAL, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); return $configValue ?: static::DEFAULT_ONLINE_MINUTES_INTERVAL; } diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml new file mode 100644 index 0000000000000..3fc25ecf57faa --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminConfigCustomerActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="SetCustomerDataLifetimeActionGroup"> + <arguments> + <argument name="minutes" defaultValue="60" type="string"/> + </arguments> + <amOnPage url="{{AdminCustomerConfigPage.url('#customer_online_customers-link')}}" stepKey="openCustomerConfigPage"/> + <fillField userInput="{{minutes}}" selector="{{AdminCustomerConfigSection.customerDataLifetime}}" stepKey="fillCustomerDataLifetime"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSave"/> + <seeElement selector="{{AdminMessagesSection.success}}" stepKey="seeSuccessMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/CustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/CustomerActionGroup.xml index 085c944cd3d3b..6a253a6053076 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/CustomerActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/CustomerActionGroup.xml @@ -18,4 +18,9 @@ <fillField stepKey="fillPassword" userInput="{{customer.password}}" selector="{{StorefrontCustomerSignInFormSection.passwordField}}"/> <click stepKey="clickSignInAccountButton" selector="{{StorefrontCustomerSignInFormSection.signInAccountButton}}"/> </actionGroup> + + <actionGroup name="CustomerLogoutStorefrontActionGroup"> + <amOnPage url="customer/account/logout/" stepKey="storefrontSignOut"/> + <waitForPageLoad time="30" stepKey="waitForLogOut"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml index e4420f189900e..c8ba70c7f8b50 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml @@ -7,7 +7,7 @@ --> <entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> <entity name="CustomerAddressSimple" type="address"> <data key="id">0</data> <data key="customer_id">12</data> @@ -89,4 +89,17 @@ <data key="default_shipping">Yes</data> <requiredEntity type="region">RegionNY</requiredEntity> </entity> + <entity name="UK_Default_Address" type="address"> + <data key="firstname">Jane</data> + <data key="lastname">Doe</data> + <data key="company">Magento</data> + <array key="street"> + <item>172, Westminster Bridge Rd</item> + </array> + <data key="city">London</data> + <data key="state"></data> + <data key="postcode">SE1 7RW</data> + <data key="country_id">GB</data> + <data key="telephone">444-44-444-44</data> + </entity> </entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml new file mode 100644 index 0000000000000..d7fc22f085344 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ApiCustomerGroup" type="customerGroup"> + <data key="code" unique="suffix">ApiCustomerGroup</data> + <data key="tax_class_id">0</data> + <data key="tax_class_name">Retail Customer</data> + </entity> +</entities> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml b/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml index 99741a357109e..9f31e5994eaae 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/RegionData.xml @@ -15,7 +15,7 @@ <entity name="RegionTX" type="region"> <data key="region">Texas</data> <data key="region_code">TX</data> - <data key="region_id">1</data> + <data key="region_id">57</data> </entity> <entity name="RegionCA" type="region"> <data key="region">California</data> diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer-group-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer-group-meta.xml new file mode 100644 index 0000000000000..a45bf1645d088 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer-group-meta.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateCustomerGroup" dataType="customerGroup" type="create" auth="adminOauth" url="/V1/customerGroups" method="POST"> + <contentType>application/json</contentType> + <object dataType="customerGroup" key="group"> + <field key="code">string</field> + <field key="tax_class_id">integer</field> + <field key="tax_class_name">string</field> + <array key="extension_attributes"> + <value>extension_attributes</value> + </array> + </object> + </operation> + <operation name="DeleteCustomerGroup" dataType="customerGroup" type="delete" auth="adminOauth" url="/V1/customerGroups/{id}" method="DELETE"> + <contentType>application/json</contentType> + </operation> +</operations> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml new file mode 100644 index 0000000000000..3cf8490ec4af1 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/AdminCustomerConfigPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminCustomerConfigPage" url="admin/system_config/edit/section/customer/{{tabLink}}" area="admin" parameterized="true" module="Magento_Customer"> + <section name="AdminCustomerConfigSection"/> + </page> +</pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressPage.xml new file mode 100644 index 0000000000000..bdbbcab67da67 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAddressPage.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="StorefrontCustomerAddressesPage" url="/customer/address/" area="storefront" module="Magento_Customer"> + <section name="StorefrontCustomerAddressesSection"/> + </page> +</pages> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml index 941e247e18b8c..788a23fb539c5 100644 --- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerDashboardPage.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> <page name="StorefrontCustomerDashboardPage" url="/customer/account/" area="storefront" module="Magento_Customer"> <section name="StorefrontCustomerDashboardAccountInformationSection" /> + <section name="StorefrontCustomerSidebarSection"/> </page> </pages> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Page/TemplatePageFile.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml similarity index 76% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Page/TemplatePageFile.xml rename to app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml index 3c4d85019fe54..4e89e5476c3bc 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Page/TemplatePageFile.xml +++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignOutPage.xml @@ -8,7 +8,5 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> - <page name="" url="" area="admin" module=""> - <section name=""/> - </page> + <page name="StorefrontCustomerSignOutPage" url="/customer/account/logout/" area="storefront" module="Magento_Customer"/> </pages> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml new file mode 100644 index 0000000000000..7eef792f0f048 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml @@ -0,0 +1,20 @@ +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminCustomerAccountInformationSection"> + <element name="accountInformationTitle" type="text" selector=".admin__page-nav-title"/> + <element name="accountInformationButton" type="text" selector="//a/span[text()='Account Information']"/> + <element name="firstName" type="input" selector="input[name='customer[firstname]']"/> + <element name="lastName" type="input" selector="input[name='customer[lastname]']"/> + <element name="email" type="input" selector="input[name='customer[email]']"/> + <element name="group" type="select" selector="[name='customer[group_id]']"/> + <element name="groupValue" type="button" selector="//span[text()='{{groupValue}}']" parameterized="true"/> + <element name="associateToWebsite" type="select" selector="select[name='customer[website_id]']"/> + <element name="storeView" type="select" selector="select[name='customer[sendemail_store_id]']"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml new file mode 100644 index 0000000000000..33696fbd616e3 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCustomerConfigSection"> + <element name="customerDataLifetime" type="input" selector="#customer_online_customers_section_data_lifetime"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml new file mode 100644 index 0000000000000..88b46d245105f --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerAddressesSection"> + <element name="addressesList" type="text" selector=".block-addresses-list" /> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderSection.xml index c39dfef5f74e7..7957fa0350fa5 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 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerOrderSection"> + <element name="isMyOrdersSection" type="text" selector="//*[@class='page-title']//*[contains(text(), 'My Orders')]"/> <element name="productCustomOptions" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd[normalize-space(.)='{{var3}}']" parameterized="true"/> <element name="productCustomOptionsFile" type="text" selector="//strong[contains(@class, 'product-item-name') and normalize-space(.)='{{var1}}']/following-sibling::*[contains(@class, 'item-options')]/dt[normalize-space(.)='{{var2}}']/following-sibling::dd[contains(.,'{{var3}}')]" parameterized="true"/> </section> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml index 52aa077fff8db..f6a6cb2d457e1 100644 --- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerOrderViewSection.xml @@ -7,11 +7,14 @@ --> <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontCustomerOrderViewSection"> <element name="orderTitle" type="text" selector=".page-title span"/> <element name="myOrdersTable" type="text" selector="#my-orders-table"/> <element name="subtotal" type="text" selector=".subtotal .amount"/> <element name="paymentMethod" type="text" selector=".payment-method dt"/> + <element name="printOrderLink" type="text" selector="a.action.print" timeout="30"/> + <element name="shippingAddress" type="text" selector=".box.box-order-shipping-address"/> + <element name="billingAddress" type="text" selector=".box.box-order-billing-address"/> </section> </sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml new file mode 100644 index 0000000000000..7482193031091 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="StorefrontCustomerSidebarSection"> + <element name="sidebarTab" type="text" selector="//div[@id='block-collapsible-nav']//a[text()='{{var1}}']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml index e5dc187e8ab7a..8e4d69e6a270d 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerTest.xml @@ -7,7 +7,7 @@ --> <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminCreateCustomerTest"> <annotations> <features value="Customer Creation"/> @@ -19,6 +19,9 @@ <group value="customer"/> <group value="create"/> </annotations> + <before> + <magentoCLI command="indexer:reindex customer_grid" stepKey="reindexCustomerGrid"/> + </before> <after> <actionGroup ref="logout" stepKey="logout"/> </after> diff --git a/app/code/Magento/Customer/Test/Mftf/composer.json b/app/code/Magento/Customer/Test/Mftf/composer.json deleted file mode 100644 index 4290fbad458c6..0000000000000 --- a/app/code/Magento/Customer/Test/Mftf/composer.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "magento/functional-test-module-customer", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-newsletter": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-review": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-integration": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-customer-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/CreatePasswordTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/CreatePasswordTest.php deleted file mode 100644 index 77f41024ba02f..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/CreatePasswordTest.php +++ /dev/null @@ -1,233 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Test\Unit\Controller\Account; - -use Magento\Framework\Controller\Result\Redirect; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class CreatePasswordTest extends \PHPUnit\Framework\TestCase -{ - /** @var \Magento\Customer\Controller\Account\CreatePassword */ - protected $model; - - /** @var ObjectManagerHelper */ - protected $objectManagerHelper; - - /** @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject */ - protected $sessionMock; - - /** @var \Magento\Framework\View\Result\PageFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $pageFactoryMock; - - /** @var \Magento\Customer\Api\AccountManagementInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $accountManagementMock; - - /** @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $requestMock; - - /** @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $redirectFactoryMock; - - /** @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $messageManagerMock; - - protected function setUp() - { - $this->sessionMock = $this->getMockBuilder(\Magento\Customer\Model\Session::class) - ->disableOriginalConstructor() - ->setMethods(['setRpToken', 'setRpCustomerId', 'getRpToken', 'getRpCustomerId']) - ->getMock(); - $this->pageFactoryMock = $this->getMockBuilder(\Magento\Framework\View\Result\PageFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->accountManagementMock = $this->getMockBuilder(\Magento\Customer\Api\AccountManagementInterface::class) - ->getMockForAbstractClass(); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) - ->getMockForAbstractClass(); - $this->redirectFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\RedirectFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) - ->getMockForAbstractClass(); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->model = $this->objectManagerHelper->getObject( - \Magento\Customer\Controller\Account\CreatePassword::class, - [ - 'customerSession' => $this->sessionMock, - 'resultPageFactory' => $this->pageFactoryMock, - 'accountManagement' => $this->accountManagementMock, - 'request' => $this->requestMock, - 'resultRedirectFactory' => $this->redirectFactoryMock, - 'messageManager' => $this->messageManagerMock, - ] - ); - } - - public function testExecuteWithLink() - { - $token = 'token'; - $customerId = '11'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['token', null, $token], - ['id', null, $customerId], - ] - ); - - $this->accountManagementMock->expects($this->once()) - ->method('validateResetPasswordLinkToken') - ->with($customerId, $token) - ->willReturn(true); - - $this->sessionMock->expects($this->once()) - ->method('setRpToken') - ->with($token); - $this->sessionMock->expects($this->once()) - ->method('setRpCustomerId') - ->with($customerId); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createpassword', []) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - public function testExecuteWithSession() - { - $token = 'token'; - $customerId = '11'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['token', null, null], - ['id', null, $customerId], - ] - ); - - $this->sessionMock->expects($this->once()) - ->method('getRpToken') - ->willReturn($token); - $this->sessionMock->expects($this->once()) - ->method('getRpCustomerId') - ->willReturn($customerId); - - $this->accountManagementMock->expects($this->once()) - ->method('validateResetPasswordLinkToken') - ->with($customerId, $token) - ->willReturn(true); - - /** @var \Magento\Framework\View\Result\Page|\PHPUnit_Framework_MockObject_MockObject $pageMock */ - $pageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->pageFactoryMock->expects($this->once()) - ->method('create') - ->with(false, []) - ->willReturn($pageMock); - - /** @var \Magento\Framework\View\Layout|\PHPUnit_Framework_MockObject_MockObject $layoutMock */ - $layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class) - ->disableOriginalConstructor() - ->getMock(); - - $pageMock->expects($this->once()) - ->method('getLayout') - ->willReturn($layoutMock); - - /** @var \Magento\Customer\Block\Account\Resetpassword|\PHPUnit_Framework_MockObject_MockObject $layoutMock */ - $blockMock = $this->getMockBuilder(\Magento\Customer\Block\Account\Resetpassword::class) - ->disableOriginalConstructor() - ->setMethods(['setCustomerId', 'setResetPasswordLinkToken']) - ->getMock(); - - $layoutMock->expects($this->once()) - ->method('getBlock') - ->with('resetPassword') - ->willReturn($blockMock); - - $blockMock->expects($this->once()) - ->method('setCustomerId') - ->with($customerId) - ->willReturnSelf(); - $blockMock->expects($this->once()) - ->method('setResetPasswordLinkToken') - ->with($token) - ->willReturnSelf(); - - $this->assertEquals($pageMock, $this->model->execute()); - } - - public function testExecuteWithException() - { - $token = 'token'; - $customerId = '11'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['token', null, $token], - ['id', null, null], - ] - ); - - $this->sessionMock->expects($this->once()) - ->method('getRpToken') - ->willReturn($token); - $this->sessionMock->expects($this->once()) - ->method('getRpCustomerId') - ->willReturn($customerId); - - $this->accountManagementMock->expects($this->once()) - ->method('validateResetPasswordLinkToken') - ->with($customerId, $token) - ->willThrowException(new \Exception('Exception.')); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('Your password reset link has expired.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/forgotpassword', []) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Account/ResetPasswordPostTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Account/ResetPasswordPostTest.php deleted file mode 100644 index b79ad008e5e44..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Account/ResetPasswordPostTest.php +++ /dev/null @@ -1,379 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Test\Unit\Controller\Account; - -use Magento\Framework\Controller\Result\Redirect; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class ResetPasswordPostTest extends \PHPUnit\Framework\TestCase -{ - /** @var \Magento\Customer\Controller\Account\ResetPasswordPost */ - protected $model; - - /** @var ObjectManagerHelper */ - protected $objectManagerHelper; - - /** @var \Magento\Customer\Model\Session|\PHPUnit_Framework_MockObject_MockObject */ - protected $sessionMock; - - /** @var \Magento\Framework\View\Result\PageFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $pageFactoryMock; - - /** @var \Magento\Customer\Api\AccountManagementInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $accountManagementMock; - - /** @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerRepositoryMock; - - /** @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $requestMock; - - /** @var \Magento\Framework\Controller\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $redirectFactoryMock; - - /** @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $messageManagerMock; - - protected function setUp() - { - $this->sessionMock = $this->getMockBuilder(\Magento\Customer\Model\Session::class) - ->disableOriginalConstructor() - ->setMethods(['unsRpToken', 'unsRpCustomerId']) - ->getMock(); - $this->pageFactoryMock = $this->getMockBuilder(\Magento\Framework\View\Result\PageFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->accountManagementMock = $this->getMockBuilder(\Magento\Customer\Api\AccountManagementInterface::class) - ->getMockForAbstractClass(); - $this->customerRepositoryMock = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) - ->setMethods(['getQuery', 'getPost']) - ->getMockForAbstractClass(); - $this->redirectFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\RedirectFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) - ->getMockForAbstractClass(); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->model = $this->objectManagerHelper->getObject( - \Magento\Customer\Controller\Account\ResetPasswordPost::class, - [ - 'customerSession' => $this->sessionMock, - 'resultPageFactory' => $this->pageFactoryMock, - 'accountManagement' => $this->accountManagementMock, - 'customerRepository' => $this->customerRepositoryMock, - 'request' => $this->requestMock, - 'resultRedirectFactory' => $this->redirectFactoryMock, - 'messageManager' => $this->messageManagerMock, - ] - ); - } - - public function testExecute() - { - $token = 'token'; - $customerId = '11'; - $password = 'password'; - $passwordConfirmation = 'password'; - $email = 'email@email.com'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customerMock */ - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $this->customerRepositoryMock->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customerMock); - - $customerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($email); - - $this->accountManagementMock->expects($this->once()) - ->method('resetPassword') - ->with($email, $token, $password) - ->willReturn(true); - - $this->sessionMock->expects($this->once()) - ->method('unsRpToken'); - $this->sessionMock->expects($this->once()) - ->method('unsRpCustomerId'); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with(__('You updated your password.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/login', []) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - public function testExecuteWithException() - { - $token = 'token'; - $customerId = '11'; - $password = 'password'; - $passwordConfirmation = 'password'; - $email = 'email@email.com'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customerMock */ - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $this->customerRepositoryMock->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customerMock); - - $customerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($email); - - $this->accountManagementMock->expects($this->once()) - ->method('resetPassword') - ->with($email, $token, $password) - ->willThrowException(new \Exception('Exception.')); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('Something went wrong while saving the new password.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createPassword', ['id' => $customerId, 'token' => $token]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - /** - * Test for InputException - */ - public function testExecuteWithInputException() - { - $token = 'token'; - $customerId = '11'; - $password = 'password'; - $passwordConfirmation = 'password'; - $email = 'email@email.com'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - /** @var \Magento\Customer\Api\Data\CustomerInterface|\PHPUnit_Framework_MockObject_MockObject $customerMock */ - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $this->customerRepositoryMock->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customerMock); - - $customerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($email); - - $this->accountManagementMock->expects($this->once()) - ->method('resetPassword') - ->with($email, $token, $password) - ->willThrowException(new \Magento\Framework\Exception\InputException(__('InputException.'))); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('InputException.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createPassword', ['id' => $customerId, 'token' => $token]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - public function testExecuteWithWrongConfirmation() - { - $token = 'token'; - $customerId = '11'; - $password = 'password'; - $passwordConfirmation = 'wrong_password'; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('New Password and Confirm New Password values didn\'t match.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createPassword', ['id' => $customerId, 'token' => $token]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - public function testExecuteWithEmptyPassword() - { - $token = 'token'; - $customerId = '11'; - $password = ''; - $passwordConfirmation = ''; - - $this->requestMock->expects($this->exactly(2)) - ->method('getQuery') - ->willReturnMap( - [ - ['token', $token], - ['id', $customerId], - ] - ); - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['password', $password], - ['password_confirmation', $passwordConfirmation], - ] - ); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('Please enter a new password.')) - ->willReturnSelf(); - - /** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->redirectFactoryMock->expects($this->once()) - ->method('create') - ->with([]) - ->willReturn($redirectMock); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('*/*/createPassword', ['id' => $customerId, 'token' => $token]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php index 913c41070856e..78d9dd7003522 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/InlineEditTest.php @@ -6,6 +6,7 @@ namespace Magento\Customer\Test\Unit\Controller\Adminhtml\Index; use Magento\Customer\Model\EmailNotificationInterface; +use Magento\Framework\Message\MessageInterface; /** * @SuppressWarnings(PHPMD.TooManyFields) @@ -242,10 +243,11 @@ protected function prepareMocksForErrorMessagesProcessing() ->method('getMessages') ->willReturn($this->messageCollection); $this->messageCollection->expects($this->once()) - ->method('getItems') + ->method('getErrors') ->willReturn([$this->message]); $this->messageCollection->expects($this->once()) - ->method('getCount') + ->method('getCountByType') + ->with(MessageInterface::TYPE_ERROR) ->willReturn(1); $this->message->expects($this->once()) ->method('getText') diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php deleted file mode 100644 index 884aab711d168..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassAssignGroupTest.php +++ /dev/null @@ -1,189 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Customer\Test\Unit\Controller\Adminhtml\Index; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassAssignGroupTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassAssignGroupTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Customer\Controller\Adminhtml\Index\MassAssignGroup - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Customer\Model\ResourceModel\Customer\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerCollectionMock; - - /** - * @var \Magento\Customer\Model\ResourceModel\Customer\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerRepositoryMock; - - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor()->getMock(); - $this->objectManagerMock = $this->createPartialMock( - \Magento\Framework\ObjectManager\ObjectManager::class, - ['create'] - ); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - $this->customerCollectionMock = - $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->customerCollectionFactoryMock = - $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\CollectionFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - - $this->resultRedirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->any()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->customerCollectionMock) - ->willReturnArgument(0); - $this->customerCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->customerCollectionMock); - $this->customerRepositoryMock = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->massAction = $objectManagerHelper->getObject( - \Magento\Customer\Controller\Adminhtml\Index\MassAssignGroup::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->customerCollectionFactoryMock, - 'customerRepository' => $this->customerRepositoryMock, - ] - ); - } - - public function testExecute() - { - $customersIds = [10, 11, 12]; - $customerMock = $this->getMockBuilder( - \Magento\Customer\Api\Data\CustomerInterface::class - )->getMockForAbstractClass(); - $this->customerCollectionMock->expects($this->any()) - ->method('getAllIds') - ->willReturn($customersIds); - - $this->customerRepositoryMock->expects($this->any()) - ->method('getById') - ->willReturnMap([[10, $customerMock], [11, $customerMock], [12, $customerMock]]); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with(__('A total of %1 record(s) were updated.', count($customersIds))); - - $this->resultRedirectMock->expects($this->any()) - ->method('setPath') - ->with('customer/*/index') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - public function testExecuteWithException() - { - $customersIds = [10, 11, 12]; - - $this->customerCollectionMock->expects($this->any()) - ->method('getAllIds') - ->willReturn($customersIds); - - $this->customerRepositoryMock->expects($this->any()) - ->method('getById') - ->willThrowException(new \Exception('Some message.')); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('Some message.'); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php deleted file mode 100644 index 190ff2c06618f..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassDeleteTest.php +++ /dev/null @@ -1,187 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Customer\Test\Unit\Controller\Adminhtml\Index; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassDeleteTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassDeleteTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Customer\Controller\Adminhtml\Index\MassDelete - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Customer\Model\ResourceModel\Customer\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerCollectionMock; - - /** - * @var \Magento\Customer\Model\ResourceModel\Customer\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerRepositoryMock; - - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor()->getMock(); - $this->objectManagerMock = $this->createPartialMock( - \Magento\Framework\ObjectManager\ObjectManager::class, - ['create'] - ); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - $this->customerCollectionMock = - $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->customerCollectionFactoryMock = - $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\CollectionFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - - $this->resultRedirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->any()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->customerCollectionMock) - ->willReturnArgument(0); - $this->customerCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->customerCollectionMock); - $this->customerRepositoryMock = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->massAction = $objectManagerHelper->getObject( - \Magento\Customer\Controller\Adminhtml\Index\MassDelete::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->customerCollectionFactoryMock, - 'customerRepository' => $this->customerRepositoryMock, - ] - ); - } - - public function testExecute() - { - $customersIds = [10, 11, 12]; - - $this->customerCollectionMock->expects($this->any()) - ->method('getAllIds') - ->willReturn($customersIds); - - $this->customerRepositoryMock->expects($this->any()) - ->method('deleteById') - ->willReturnMap([[10, true], [11, true], [12, true]]); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with(__('A total of %1 record(s) were deleted.', count($customersIds))); - - $this->resultRedirectMock->expects($this->any()) - ->method('setPath') - ->with('customer/*/index') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - public function testExecuteWithException() - { - $customersIds = [10, 11, 12]; - - $this->customerCollectionMock->expects($this->any()) - ->method('getAllIds') - ->willReturn($customersIds); - - $this->customerRepositoryMock->expects($this->any()) - ->method('deleteById') - ->willThrowException(new \Exception('Some message.')); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('Some message.'); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php deleted file mode 100644 index daf9c64fe7b7b..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassSubscribeTest.php +++ /dev/null @@ -1,203 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Customer\Test\Unit\Controller\Adminhtml\Index; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassSubscribeTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassSubscribeTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Customer\Controller\Adminhtml\Index\MassSubscribe - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Customer\Model\ResourceModel\Customer\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerCollectionMock; - - /** - * @var \Magento\Customer\Model\ResourceModel\Customer\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerRepositoryMock; - - /** - * @var \Magento\Newsletter\Model\Subscriber|\PHPUnit_Framework_MockObject_MockObject - */ - protected $subscriberMock; - - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor()->getMock(); - $this->objectManagerMock = $this->createPartialMock( - \Magento\Framework\ObjectManager\ObjectManager::class, - ['create'] - ); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - $this->customerCollectionMock = - $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->customerCollectionFactoryMock = - $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\CollectionFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - $this->subscriberMock = $this->createMock(\Magento\Newsletter\Model\Subscriber::class); - $subscriberFactoryMock = $this->getMockBuilder(\Magento\Newsletter\Model\SubscriberFactory::class) - ->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); - $subscriberFactoryMock->expects($this->any()) - ->method('create') - ->willReturn($this->subscriberMock); - - $this->resultRedirectMock = $this->createMock(\Magento\Backend\Model\View\Result\Redirect::class); - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->any()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->customerCollectionMock) - ->willReturnArgument(0); - $this->customerCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->customerCollectionMock); - $this->customerRepositoryMock = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->massAction = $objectManagerHelper->getObject( - \Magento\Customer\Controller\Adminhtml\Index\MassSubscribe::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->customerCollectionFactoryMock, - 'customerRepository' => $this->customerRepositoryMock, - 'subscriberFactory' => $subscriberFactoryMock, - ] - ); - } - - public function testExecute() - { - $customersIds = [10, 11, 12]; - - $this->customerCollectionMock->expects($this->any()) - ->method('getAllIds') - ->willReturn($customersIds); - - $this->customerRepositoryMock->expects($this->any()) - ->method('getById') - ->willReturnMap([[10, true], [11, true], [12, true]]); - - $this->subscriberMock->expects($this->any()) - ->method('subscribeCustomerById') - ->willReturnMap([[10, true], [11, true], [12, true]]); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with(__('A total of %1 record(s) were updated.', count($customersIds))); - - $this->resultRedirectMock->expects($this->any()) - ->method('setPath') - ->with('customer/*/index') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - public function testExecuteWithException() - { - $customersIds = [10, 11, 12]; - - $this->customerCollectionMock->expects($this->any()) - ->method('getAllIds') - ->willReturn($customersIds); - - $this->customerRepositoryMock->expects($this->any()) - ->method('getById') - ->willThrowException(new \Exception('Some message.')); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('Some message.'); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php deleted file mode 100644 index 05624661a2de4..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/MassUnsubscribeTest.php +++ /dev/null @@ -1,203 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Customer\Test\Unit\Controller\Adminhtml\Index; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassUnsubscribeTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassUnsubscribeTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Customer\Controller\Adminhtml\Index\MassUnsubscribe - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Customer\Model\ResourceModel\Customer\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerCollectionMock; - - /** - * @var \Magento\Customer\Model\ResourceModel\Customer\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customerRepositoryMock; - - /** - * @var \Magento\Newsletter\Model\Subscriber|\PHPUnit_Framework_MockObject_MockObject - */ - protected $subscriberMock; - - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor()->getMock(); - $this->objectManagerMock = $this->createPartialMock( - \Magento\Framework\ObjectManager\ObjectManager::class, - ['create'] - ); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - $this->customerCollectionMock = - $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $this->customerCollectionFactoryMock = - $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Customer\CollectionFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - $this->subscriberMock = $this->createMock(\Magento\Newsletter\Model\Subscriber::class); - $subscriberFactoryMock = $this->getMockBuilder(\Magento\Newsletter\Model\SubscriberFactory::class) - ->setMethods(['create']) - ->disableOriginalConstructor() - ->getMock(); - $subscriberFactoryMock->expects($this->any()) - ->method('create') - ->willReturn($this->subscriberMock); - - $this->resultRedirectMock = $this->createMock(\Magento\Backend\Model\View\Result\Redirect::class); - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->any()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->customerCollectionMock) - ->willReturnArgument(0); - $this->customerCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->customerCollectionMock); - $this->customerRepositoryMock = $this->getMockBuilder(\Magento\Customer\Api\CustomerRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->massAction = $objectManagerHelper->getObject( - \Magento\Customer\Controller\Adminhtml\Index\MassUnsubscribe::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->customerCollectionFactoryMock, - 'customerRepository' => $this->customerRepositoryMock, - 'subscriberFactory' => $subscriberFactoryMock, - ] - ); - } - - public function testExecute() - { - $customersIds = [10, 11, 12]; - - $this->customerCollectionMock->expects($this->any()) - ->method('getAllIds') - ->willReturn($customersIds); - - $this->customerRepositoryMock->expects($this->any()) - ->method('getById') - ->willReturnMap([[10, true], [11, true], [12, true]]); - - $this->subscriberMock->expects($this->any()) - ->method('unsubscribeCustomerById') - ->willReturnMap([[10, true], [11, true], [12, true]]); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with(__('A total of %1 record(s) were updated.', count($customersIds))); - - $this->resultRedirectMock->expects($this->any()) - ->method('setPath') - ->with('customer/*/index') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - public function testExecuteWithException() - { - $customersIds = [10, 11, 12]; - - $this->customerCollectionMock->expects($this->any()) - ->method('getAllIds') - ->willReturn($customersIds); - - $this->customerRepositoryMock->expects($this->any()) - ->method('getById') - ->willThrowException(new \Exception('Some message.')); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('Some message.'); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php index 8fc4df1515b94..04da8e77867d8 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Section/LoadTest.php @@ -81,13 +81,13 @@ protected function setUp() } /** - * @param $sectionNames - * @param $updateSectionID - * @param $sectionNamesAsArray - * @param $updateIds + * @param string $sectionNames + * @param bool $forceNewSectionTimestamp + * @param string[] $sectionNamesAsArray + * @param bool $forceNewTimestamp * @dataProvider executeDataProvider */ - public function testExecute($sectionNames, $updateSectionID, $sectionNamesAsArray, $updateIds) + public function testExecute($sectionNames, $forceNewSectionTimestamp, $sectionNamesAsArray, $forceNewTimestamp) { $this->resultJsonFactoryMock->expects($this->once()) ->method('create') @@ -101,12 +101,12 @@ public function testExecute($sectionNames, $updateSectionID, $sectionNamesAsArra $this->httpRequestMock->expects($this->exactly(2)) ->method('getParam') - ->withConsecutive(['sections'], ['update_section_id']) - ->willReturnOnConsecutiveCalls($sectionNames, $updateSectionID); + ->withConsecutive(['sections'], ['force_new_section_timestamp']) + ->willReturnOnConsecutiveCalls($sectionNames, $forceNewSectionTimestamp); $this->sectionPoolMock->expects($this->once()) ->method('getSectionsData') - ->with($sectionNamesAsArray, $updateIds) + ->with($sectionNamesAsArray, $forceNewTimestamp) ->willReturn([ 'message' => 'some message', 'someKey' => 'someValue' @@ -131,15 +131,15 @@ public function executeDataProvider() return [ [ 'sectionNames' => 'sectionName1,sectionName2,sectionName3', - 'updateSectionID' => 'updateSectionID', + 'forceNewSectionTimestamp' => 'forceNewSectionTimestamp', 'sectionNamesAsArray' => ['sectionName1', 'sectionName2', 'sectionName3'], - 'updateIds' => true + 'forceNewTimestamp' => true ], [ 'sectionNames' => null, - 'updateSectionID' => null, + 'forceNewSectionTimestamp' => null, 'sectionNamesAsArray' => null, - 'updateIds' => false + 'forceNewTimestamp' => false ], ]; } diff --git a/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php b/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php index 98fee70e335f7..2b67df1aee292 100644 --- a/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php +++ b/app/code/Magento/Customer/Test/Unit/CustomerData/SectionPoolTest.php @@ -63,7 +63,7 @@ public function testGetSectionsDataAllSections() $this->identifierMock->expects($this->once()) ->method('markSections') - //check also default value for $updateIds = false + //check also default value for $forceTimestamp = false ->with($allSectionsData, $sectionNames, false) ->willReturn($identifierResult); $modelResult = $this->model->getSectionsData($sectionNames); diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php deleted file mode 100644 index 525cb91cbb966..0000000000000 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ /dev/null @@ -1,1978 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Customer\Test\Unit\Model; - -use Magento\Customer\Model\AccountManagement; -use Magento\Customer\Model\AccountConfirmation; -use Magento\Customer\Model\AuthenticationInterface; -use Magento\Customer\Model\EmailNotificationInterface; -use Magento\Framework\App\Area; -use Magento\Framework\Exception\NoSuchEntityException; -use Magento\Framework\Intl\DateTimeFactory; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; -use Magento\Store\Model\ScopeInterface; - -/** - * @SuppressWarnings(PHPMD.TooManyFields) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class AccountManagementTest extends \PHPUnit\Framework\TestCase -{ - /** @var AccountManagement */ - protected $accountManagement; - - /** @var ObjectManagerHelper */ - protected $objectManagerHelper; - - /** @var \Magento\Customer\Model\CustomerFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerFactory; - - /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $manager; - - /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeManager; - - /** @var \Magento\Framework\Math\Random|\PHPUnit_Framework_MockObject_MockObject */ - protected $random; - - /** @var \Magento\Customer\Model\Metadata\Validator|\PHPUnit_Framework_MockObject_MockObject */ - protected $validator; - - /** @var \Magento\Customer\Api\Data\ValidationResultsInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $validationResultsInterfaceFactory; - - /** @var \Magento\Customer\Api\AddressRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $addressRepository; - - /** @var \Magento\Customer\Api\CustomerMetadataInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerMetadata; - - /** @var \Magento\Customer\Model\CustomerRegistry|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerRegistry; - - /** @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $logger; - - /** @var \Magento\Framework\Encryption\EncryptorInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $encryptor; - - /** @var \Magento\Customer\Model\Config\Share|\PHPUnit_Framework_MockObject_MockObject */ - protected $share; - - /** @var \Magento\Framework\Stdlib\StringUtils|\PHPUnit_Framework_MockObject_MockObject */ - protected $string; - - /** @var \Magento\Customer\Api\CustomerRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerRepository; - - /** @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $scopeConfig; - - /** @var \Magento\Framework\Mail\Template\TransportBuilder|\PHPUnit_Framework_MockObject_MockObject */ - protected $transportBuilder; - - /** @var \Magento\Framework\Reflection\DataObjectProcessor|\PHPUnit_Framework_MockObject_MockObject */ - protected $dataObjectProcessor; - - /** @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject */ - protected $registry; - - /** @var \Magento\Customer\Helper\View|\PHPUnit_Framework_MockObject_MockObject */ - protected $customerViewHelper; - - /** @var \Magento\Framework\Stdlib\DateTime|\PHPUnit_Framework_MockObject_MockObject */ - protected $dateTime; - - /** @var \Magento\Customer\Model\Customer|\PHPUnit_Framework_MockObject_MockObject */ - protected $customer; - - /** @var \Magento\Framework\DataObjectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectFactory; - - /** @var \Magento\Framework\Api\ExtensibleDataObjectConverter|\PHPUnit_Framework_MockObject_MockObject */ - protected $extensibleDataObjectConverter; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Store\Model\Store - */ - protected $store; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Model\Data\CustomerSecure - */ - protected $customerSecure; - - /** - * @var AuthenticationInterface |\PHPUnit_Framework_MockObject_MockObject - */ - protected $authenticationMock; - - /** - * @var EmailNotificationInterface |\PHPUnit_Framework_MockObject_MockObject - */ - protected $emailNotificationMock; - - /** - * @var DateTimeFactory|\PHPUnit_Framework_MockObject_MockObject - */ - private $dateTimeFactory; - - /** - * @var AccountConfirmation|\PHPUnit_Framework_MockObject_MockObject - */ - private $accountConfirmation; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Session\SessionManagerInterface - */ - private $sessionManager; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory - */ - private $visitorCollectionFactory; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Session\SaveHandlerInterface - */ - private $saveHandler; - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - protected function setUp() - { - $this->customerFactory = $this->createPartialMock(\Magento\Customer\Model\CustomerFactory::class, ['create']); - $this->manager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); - $this->store = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storeManager = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->random = $this->createMock(\Magento\Framework\Math\Random::class); - $this->validator = $this->createMock(\Magento\Customer\Model\Metadata\Validator::class); - $this->validationResultsInterfaceFactory = $this->createMock( - \Magento\Customer\Api\Data\ValidationResultsInterfaceFactory::class - ); - $this->addressRepository = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class); - $this->customerMetadata = $this->createMock(\Magento\Customer\Api\CustomerMetadataInterface::class); - $this->customerRegistry = $this->createMock(\Magento\Customer\Model\CustomerRegistry::class); - $this->logger = $this->createMock(\Psr\Log\LoggerInterface::class); - $this->encryptor = $this->getMockBuilder(\Magento\Framework\Encryption\EncryptorInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->share = $this->createMock(\Magento\Customer\Model\Config\Share::class); - $this->string = $this->createMock(\Magento\Framework\Stdlib\StringUtils::class); - $this->customerRepository = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); - $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->transportBuilder = $this->createMock(\Magento\Framework\Mail\Template\TransportBuilder::class); - $this->dataObjectProcessor = $this->createMock(\Magento\Framework\Reflection\DataObjectProcessor::class); - $this->registry = $this->createMock(\Magento\Framework\Registry::class); - $this->customerViewHelper = $this->createMock(\Magento\Customer\Helper\View::class); - $this->dateTime = $this->createMock(\Magento\Framework\Stdlib\DateTime::class); - $this->customer = $this->createMock(\Magento\Customer\Model\Customer::class); - $this->objectFactory = $this->createMock(\Magento\Framework\DataObjectFactory::class); - $this->extensibleDataObjectConverter = $this->createMock( - \Magento\Framework\Api\ExtensibleDataObjectConverter::class - ); - $this->authenticationMock = $this->getMockBuilder(AuthenticationInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->emailNotificationMock = $this->getMockBuilder(EmailNotificationInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['setRpToken', 'addData', 'setRpTokenCreatedAt', 'setData', 'getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - - $this->visitorCollectionFactory = $this->getMockBuilder( - \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class - ) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->saveHandler = $this->getMockBuilder(\Magento\Framework\Session\SaveHandlerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->dateTimeFactory = $this->createMock(DateTimeFactory::class); - $this->accountConfirmation = $this->createMock(AccountConfirmation::class); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->accountManagement = $this->objectManagerHelper->getObject( - \Magento\Customer\Model\AccountManagement::class, - [ - 'customerFactory' => $this->customerFactory, - 'eventManager' => $this->manager, - 'storeManager' => $this->storeManager, - 'mathRandom' => $this->random, - 'validator' => $this->validator, - 'validationResultsDataFactory' => $this->validationResultsInterfaceFactory, - 'addressRepository' => $this->addressRepository, - 'customerMetadataService' => $this->customerMetadata, - 'customerRegistry' => $this->customerRegistry, - 'logger' => $this->logger, - 'encryptor' => $this->encryptor, - 'configShare' => $this->share, - 'stringHelper' => $this->string, - 'customerRepository' => $this->customerRepository, - 'scopeConfig' => $this->scopeConfig, - 'transportBuilder' => $this->transportBuilder, - 'dataProcessor' => $this->dataObjectProcessor, - 'registry' => $this->registry, - 'customerViewHelper' => $this->customerViewHelper, - 'dateTime' => $this->dateTime, - 'customerModel' => $this->customer, - 'objectFactory' => $this->objectFactory, - 'extensibleDataObjectConverter' => $this->extensibleDataObjectConverter, - 'dateTimeFactory' => $this->dateTimeFactory, - 'accountConfirmation' => $this->accountConfirmation, - 'sessionManager' => $this->sessionManager, - 'saveHandler' => $this->saveHandler, - 'visitorCollectionFactory' => $this->visitorCollectionFactory, - ] - ); - $reflection = new \ReflectionClass(get_class($this->accountManagement)); - $reflectionProperty = $reflection->getProperty('authentication'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($this->accountManagement, $this->authenticationMock); - $reflectionProperty = $reflection->getProperty('emailNotification'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($this->accountManagement, $this->emailNotificationMock); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - */ - public function testCreateAccountWithPasswordHashWithExistingCustomer() - { - $websiteId = 1; - $storeId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->once()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->once()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager - ->expects($this->once()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->accountManagement->createAccountWithPasswordHash($customer, $hash); - } - - /** - * @expectedException \Magento\Framework\Exception\State\InputMismatchException - */ - public function testCreateAccountWithPasswordHashWithCustomerWithoutStoreId() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer - ->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer - ->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $exception = new \Magento\Framework\Exception\AlreadyExistsException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customer, $hash) - ->willThrowException($exception); - - $this->accountManagement->createAccountWithPasswordHash($customer, $hash); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithPasswordHashWithLocalizedException() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer - ->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer - ->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $exception = new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customer, $hash) - ->willThrowException($exception); - - $this->accountManagement->createAccountWithPasswordHash($customer, $hash); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithPasswordHashWithAddressException() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->once()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer - ->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer - ->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share - ->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customer, $hash) - ->willReturn($customer); - $exception = new \Magento\Framework\Exception\InputException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->addressRepository - ->expects($this->atLeastOnce()) - ->method('save') - ->with($address) - ->willThrowException($exception); - $this->customerRepository - ->expects($this->once()) - ->method('delete') - ->with($customer); - - $this->accountManagement->createAccountWithPasswordHash($customer, $hash); - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithPasswordHashWithNewCustomerAndLocalizedException() - { - $storeId = 1; - $storeName = 'store_name'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - - $customerMock->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn(null); - $customerMock->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customerMock->expects($this->once()) - ->method('setCreatedIn') - ->with($storeName) - ->willReturnSelf(); - $customerMock->expects($this->once()) - ->method('getAddresses') - ->willReturn([]); - $customerMock->expects($this->once()) - ->method('setAddresses') - ->with(null) - ->willReturnSelf(); - - $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - - $storeMock->expects($this->once()) - ->method('getName') - ->willReturn($storeName); - - $this->storeManager->expects($this->exactly(2)) - ->method('getStore') - ->with($storeId) - ->willReturn($storeMock); - $exception = new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customerMock, $hash) - ->willThrowException($exception); - - $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testCreateAccountWithoutPassword() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; - - $datetime = $this->prepareDateTimeFactory(); - - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->atLeastOnce()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->atLeastOnce()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->customerRepository->expects($this->atLeastOnce()) - ->method('save') - ->willReturn($customer); - $this->addressRepository->expects($this->atLeastOnce()) - ->method('save') - ->with($address); - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customer); - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($newLinkToken); - $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - $customerSecure->expects($this->any()) - ->method('setRpToken') - ->with($newLinkToken); - $customerSecure->expects($this->any()) - ->method('setRpTokenCreatedAt') - ->with($datetime) - ->willReturnSelf(); - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn(null); - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($customerSecure); - $this->emailNotificationMock->expects($this->once()) - ->method('newAccount') - ->willReturnSelf(); - - $this->accountManagement->createAccount($customer); - } - - /** - * Data provider for testCreateAccountWithPasswordInputException test - * - * @return array - */ - public function dataProviderCheckPasswordStrength() - { - return [ - [ - 'testNumber' => 1, - 'password' => 'qwer', - 'minPasswordLength' => 5, - 'minCharacterSetsNum' => 1 - ], - [ - 'testNumber' => 2, - 'password' => 'wrfewqedf1', - 'minPasswordLength' => 5, - 'minCharacterSetsNum' => 3 - ] - ]; - } - - /** - * @param int $testNumber - * @param string $password - * @param int $minPasswordLength - * @param int $minCharacterSetsNum - * @dataProvider dataProviderCheckPasswordStrength - */ - public function testCreateAccountWithPasswordInputException( - $testNumber, - $password, - $minPasswordLength, - $minCharacterSetsNum - ) { - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->will( - $this->returnValueMap( - [ - [ - AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, - 'default', - null, - $minPasswordLength, - ], - [ - AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, - 'default', - null, - $minCharacterSetsNum], - ] - ) - ); - - $this->string->expects($this->any()) - ->method('strlen') - ->with($password) - ->willReturn(iconv_strlen($password, 'UTF-8')); - - if ($testNumber == 1) { - $this->expectException( - \Magento\Framework\Exception\InputException::class, - 'Please enter a password with at least ' . $minPasswordLength . ' characters.' - ); - } - - if ($testNumber == 2) { - $this->expectException( - \Magento\Framework\Exception\InputException::class, - 'Minimum of different classes of characters in password is ' . $minCharacterSetsNum . - '. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.' - ); - } - - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $this->accountManagement->createAccount($customer, $password); - } - - public function testCreateAccountInputExceptionExtraLongPassword() - { - $password = '257*chars*************************************************************************************' - . '****************************************************************************************************' - . '***************************************************************'; - - $this->string->expects($this->any()) - ->method('strlen') - ->with($password) - ->willReturn(iconv_strlen($password, 'UTF-8')); - - $this->expectException( - \Magento\Framework\Exception\InputException::class, - 'Please enter a password with at most 256 characters.' - ); - - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $this->accountManagement->createAccount($customer, $password); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testCreateAccountWithPassword() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - $password = 'wrfewqedf1'; - $minPasswordLength = 5; - $minCharacterSetsNum = 2; - - $datetime = $this->prepareDateTimeFactory(); - - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->willReturnMap( - [ - [ - AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, - 'default', - null, - $minPasswordLength, - ], - [ - AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, - 'default', - null, - $minCharacterSetsNum], - [ - AccountManagement::XML_PATH_REGISTER_EMAIL_TEMPLATE, - ScopeInterface::SCOPE_STORE, - $defaultStoreId, - $templateIdentifier, - ], - [ - AccountManagement::XML_PATH_REGISTER_EMAIL_IDENTITY, - ScopeInterface::SCOPE_STORE, - 1, - $sender - ] - ] - ); - $this->string->expects($this->any()) - ->method('strlen') - ->with($password) - ->willReturn(iconv_strlen($password, 'UTF-8')); - $this->encryptor->expects($this->once()) - ->method('getHash') - ->with($password, true) - ->willReturn($hash); - $address = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId); - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->getMockBuilder(\Magento\Store\Model\Website::class)->disableOriginalConstructor()->getMock(); - $website->expects($this->atLeastOnce()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->atLeastOnce()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->customerRepository->expects($this->atLeastOnce()) - ->method('save') - ->willReturn($customer); - $this->addressRepository->expects($this->atLeastOnce()) - ->method('save') - ->with($address); - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customer); - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($newLinkToken); - $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - $customerSecure->expects($this->any()) - ->method('setRpToken') - ->with($newLinkToken); - $customerSecure->expects($this->any()) - ->method('setRpTokenCreatedAt') - ->with($datetime) - ->willReturnSelf(); - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn($hash); - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($customerSecure); - $this->emailNotificationMock->expects($this->once()) - ->method('newAccount') - ->willReturnSelf(); - - $this->accountManagement->createAccount($customer, $password); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testSendPasswordReminderEmail() - { - $customerId = 1; - $customerStoreId = 2; - $customerEmail = 'email@email.com'; - $customerData = ['key' => 'value']; - $customerName = 'Customer Name'; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMock(); - $customer->expects($this->any()) - ->method('getStoreId') - ->willReturn($customerStoreId); - $customer->expects($this->any()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->any()) - ->method('getEmail') - ->willReturn($customerEmail); - - $this->store->expects($this->any()) - ->method('getId') - ->willReturn($customerStoreId); - - $this->storeManager->expects($this->at(0)) - ->method('getStore') - ->willReturn($this->store); - - $this->storeManager->expects($this->at(1)) - ->method('getStore') - ->with($customerStoreId) - ->willReturn($this->store); - - $this->customerRegistry->expects($this->once()) - ->method('retrieveSecureData') - ->with($customerId) - ->willReturn($this->customerSecure); - - $this->dataObjectProcessor->expects($this->once()) - ->method('buildOutputDataArray') - ->with($customer, \Magento\Customer\Api\Data\CustomerInterface::class) - ->willReturn($customerData); - - $this->customerViewHelper->expects($this->any()) - ->method('getCustomerName') - ->with($customer) - ->willReturn($customerName); - - $this->customerSecure->expects($this->once()) - ->method('addData') - ->with($customerData) - ->willReturnSelf(); - $this->customerSecure->expects($this->once()) - ->method('setData') - ->with('name', $customerName) - ->willReturnSelf(); - - $this->scopeConfig->expects($this->at(0)) - ->method('getValue') - ->with(AccountManagement::XML_PATH_REMIND_EMAIL_TEMPLATE, ScopeInterface::SCOPE_STORE, $customerStoreId) - ->willReturn($templateIdentifier); - $this->scopeConfig->expects($this->at(1)) - ->method('getValue') - ->with(AccountManagement::XML_PATH_FORGOT_EMAIL_IDENTITY, ScopeInterface::SCOPE_STORE, $customerStoreId) - ->willReturn($sender); - - $transport = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) - ->getMock(); - - $this->transportBuilder->expects($this->once()) - ->method('setTemplateIdentifier') - ->with($templateIdentifier) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setTemplateOptions') - ->with(['area' => Area::AREA_FRONTEND, 'store' => $customerStoreId]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setTemplateVars') - ->with(['customer' => $this->customerSecure, 'store' => $this->store]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setFrom') - ->with($sender) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('addTo') - ->with($customerEmail, $customerName) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('getTransport') - ->willReturn($transport); - - $transport->expects($this->once()) - ->method('sendMessage'); - - $this->assertEquals($this->accountManagement, $this->accountManagement->sendPasswordReminderEmail($customer)); - } - - /** - * @param string $email - * @param string $templateIdentifier - * @param string $sender - * @param int $storeId - * @param int $customerId - * @param string $hash - */ - protected function prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash) - { - $websiteId = 1; - - $datetime = $this->prepareDateTimeFactory(); - - $customerData = ['key' => 'value']; - $customerName = 'Customer Name'; - - $this->store->expects($this->once()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $this->store->expects($this->any()) - ->method('getId') - ->willReturn($storeId); - - $this->storeManager->expects($this->any()) - ->method('getStore') - ->willReturn($this->store); - - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMock(); - $customer->expects($this->any()) - ->method('getEmail') - ->willReturn($email); - $customer->expects($this->any()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->any()) - ->method('getStoreId') - ->willReturn($storeId); - - $this->customerRepository->expects($this->once()) - ->method('get') - ->with($email, $websiteId) - ->willReturn($customer); - $this->customerRepository->expects($this->once()) - ->method('save') - ->with($customer) - ->willReturnSelf(); - - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($hash); - - $this->customerViewHelper->expects($this->any()) - ->method('getCustomerName') - ->with($customer) - ->willReturn($customerName); - - $this->customerSecure->expects($this->any()) - ->method('setRpToken') - ->with($hash) - ->willReturnSelf(); - $this->customerSecure->expects($this->any()) - ->method('setRpTokenCreatedAt') - ->with($datetime) - ->willReturnSelf(); - $this->customerSecure->expects($this->any()) - ->method('addData') - ->with($customerData) - ->willReturnSelf(); - $this->customerSecure->expects($this->any()) - ->method('setData') - ->with('name', $customerName) - ->willReturnSelf(); - - $this->customerRegistry->expects($this->any()) - ->method('retrieveSecureData') - ->with($customerId) - ->willReturn($this->customerSecure); - - $this->dataObjectProcessor->expects($this->any()) - ->method('buildOutputDataArray') - ->with($customer, \Magento\Customer\Api\Data\CustomerInterface::class) - ->willReturn($customerData); - - $this->prepareEmailSend($email, $templateIdentifier, $sender, $storeId, $customerName); - } - - /** - * @param $email - * @param $templateIdentifier - * @param $sender - * @param $storeId - * @param $customerName - */ - protected function prepareEmailSend($email, $templateIdentifier, $sender, $storeId, $customerName) - { - $transport = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) - ->getMock(); - - $this->transportBuilder->expects($this->any()) - ->method('setTemplateIdentifier') - ->with($templateIdentifier) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('setTemplateOptions') - ->with(['area' => Area::AREA_FRONTEND, 'store' => $storeId]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('setTemplateVars') - ->with(['customer' => $this->customerSecure, 'store' => $this->store]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('setFrom') - ->with($sender) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('addTo') - ->with($email, $customerName) - ->willReturnSelf(); - $this->transportBuilder->expects($this->any()) - ->method('getTransport') - ->willReturn($transport); - - $transport->expects($this->any()) - ->method('sendMessage'); - } - - public function testInitiatePasswordResetEmailReminder() - { - $customerId = 1; - - $email = 'test@example.com'; - $template = AccountManagement::EMAIL_REMINDER; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - - $storeId = 1; - - mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); - $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); - - $this->emailNotificationMock->expects($this->once()) - ->method('passwordReminder') - ->willReturnSelf(); - - $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); - - $this->assertTrue($this->accountManagement->initiatePasswordReset($email, $template)); - } - - public function testInitiatePasswordResetEmailReset() - { - $storeId = 1; - $customerId = 1; - - $email = 'test@example.com'; - $template = AccountManagement::EMAIL_RESET; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - - mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); - $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); - - $this->emailNotificationMock->expects($this->once()) - ->method('passwordResetConfirmation') - ->willReturnSelf(); - - $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); - - $this->assertTrue($this->accountManagement->initiatePasswordReset($email, $template)); - } - - public function testInitiatePasswordResetNoTemplate() - { - $storeId = 1; - $customerId = 1; - - $email = 'test@example.com'; - $template = null; - $templateIdentifier = 'Template Identifier'; - $sender = 'Sender'; - - mt_srand(mt_rand() + (100000000 * (float)microtime()) % PHP_INT_MAX); - $hash = md5(uniqid(microtime() . mt_rand(0, mt_getrandmax()), true)); - - $this->prepareInitiatePasswordReset($email, $templateIdentifier, $sender, $storeId, $customerId, $hash); - - $this->expectException(\Magento\Framework\Exception\InputException::class); - $this->expectExceptionMessage( - 'Invalid value of "" provided for the template field. Possible values: email_reminder or email_reset.' - ); - $this->accountManagement->initiatePasswordReset($email, $template); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage Invalid value of "" provided for the customerId field - */ - public function testValidateResetPasswordTokenBadCustomerId() - { - $this->accountManagement->validateResetPasswordLinkToken(null, ''); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage resetPasswordLinkToken is a required field - */ - public function testValidateResetPasswordTokenBadResetPasswordLinkToken() - { - $this->accountManagement->validateResetPasswordLinkToken(22, null); - } - - /** - * @expectedException \Magento\Framework\Exception\State\InputMismatchException - * @expectedExceptionMessage Reset password token mismatch - */ - public function testValidateResetPasswordTokenTokenMismatch() - { - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($this->customerSecure); - - $this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken'); - } - - /** - * @expectedException \Magento\Framework\Exception\State\ExpiredException - * @expectedExceptionMessage Reset password token expired - */ - public function testValidateResetPasswordTokenTokenExpired() - { - $this->reInitModel(); - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($this->customerSecure); - - $this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken'); - } - - /** - * return bool - */ - public function testValidateResetPasswordToken() - { - $this->reInitModel(); - - $this->customer - ->expects($this->once()) - ->method('getResetPasswordLinkExpirationPeriod') - ->willReturn(100000); - - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($this->customerSecure); - - $this->assertTrue($this->accountManagement->validateResetPasswordLinkToken(22, 'newStringToken')); - } - - /** - * reInit $this->accountManagement object - */ - private function reInitModel() - { - $this->customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->disableOriginalConstructor() - ->setMethods( - [ - 'getRpToken', - 'getRpTokenCreatedAt', - 'getPasswordHash', - 'setPasswordHash', - 'setRpToken', - 'setRpTokenCreatedAt', - ] - ) - ->getMock(); - - $this->customerSecure - ->expects($this->any()) - ->method('getRpToken') - ->willReturn('newStringToken'); - - $pastDateTime = '2016-10-25 00:00:00'; - - $this->customerSecure - ->expects($this->any()) - ->method('getRpTokenCreatedAt') - ->willReturn($pastDateTime); - - $this->customer = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) - ->disableOriginalConstructor() - ->setMethods(['getResetPasswordLinkExpirationPeriod']) - ->getMock(); - - $this->prepareDateTimeFactory(); - - $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManagerInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->visitorCollectionFactory = $this->getMockBuilder( - \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class - ) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->saveHandler = $this->getMockBuilder(\Magento\Framework\Session\SaveHandlerInterface::class) - ->disableOriginalConstructor() - ->setMethods(['destroy']) - ->getMockForAbstractClass(); - - $dateTime = '2017-10-25 18:57:08'; - $timestamp = '1508983028'; - $dateTimeMock = $this->createMock(\DateTime::class); - $dateTimeMock->expects($this->any()) - ->method('format') - ->with(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) - ->willReturn($dateTime); - - $dateTimeMock - ->expects($this->any()) - ->method('getTimestamp') - ->willReturn($timestamp); - - $dateTimeMock - ->expects($this->any()) - ->method('setTimestamp') - ->willReturnSelf(); - - $dateTimeFactory = $this->createMock(DateTimeFactory::class); - $dateTimeFactory->expects($this->any())->method('create')->willReturn($dateTimeMock); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->accountManagement = $this->objectManagerHelper->getObject( - \Magento\Customer\Model\AccountManagement::class, - [ - 'customerFactory' => $this->customerFactory, - 'customerRegistry' => $this->customerRegistry, - 'customerRepository' => $this->customerRepository, - 'customerModel' => $this->customer, - 'dateTimeFactory' => $dateTimeFactory, - 'stringHelper' => $this->string, - 'scopeConfig' => $this->scopeConfig, - 'sessionManager' => $this->sessionManager, - 'visitorCollectionFactory' => $this->visitorCollectionFactory, - 'saveHandler' => $this->saveHandler, - 'encryptor' => $this->encryptor, - 'dataProcessor' => $this->dataObjectProcessor, - 'storeManager' => $this->storeManager, - 'transportBuilder' => $this->transportBuilder, - ] - ); - $reflection = new \ReflectionClass(get_class($this->accountManagement)); - $reflectionProperty = $reflection->getProperty('authentication'); - $reflectionProperty->setAccessible(true); - $reflectionProperty->setValue($this->accountManagement, $this->authenticationMock); - } - - /** - * @return void - */ - public function testChangePassword() - { - $customerId = 7; - $email = 'test@example.com'; - $currentPassword = '1234567'; - $newPassword = 'abcdefg'; - $passwordHash = '1a2b3f4c'; - - $this->reInitModel(); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMock(); - $customer->expects($this->any()) - ->method('getId') - ->willReturn($customerId); - - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($email) - ->willReturn($customer); - - $this->authenticationMock->expects($this->once()) - ->method('authenticate'); - - $this->customerSecure->expects($this->once()) - ->method('setRpToken') - ->with(null); - $this->customerSecure->expects($this->once()) - ->method('setRpTokenCreatedAt') - ->willReturnSelf(); - $this->customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn($passwordHash); - - $this->customerRegistry->expects($this->any()) - ->method('retrieveSecureData') - ->with($customerId) - ->willReturn($this->customerSecure); - - $this->scopeConfig->expects($this->any()) - ->method('getValue') - ->willReturnMap( - [ - [ - AccountManagement::XML_PATH_MINIMUM_PASSWORD_LENGTH, - 'default', - null, - 7, - ], - [ - AccountManagement::XML_PATH_REQUIRED_CHARACTER_CLASSES_NUMBER, - 'default', - null, - 1 - ], - ] - ); - $this->string->expects($this->any()) - ->method('strlen') - ->with($newPassword) - ->willReturn(7); - - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customer); - - $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); - - $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) - ->disableOriginalConstructor() - ->setMethods(['getSessionId']) - ->getMock(); - $visitor->expects($this->at(0))->method('getSessionId')->willReturn('session_id_1'); - $visitor->expects($this->at(1))->method('getSessionId')->willReturn('session_id_2'); - $visitorCollection = $this->getMockBuilder( - \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class - ) - ->disableOriginalConstructor()->setMethods(['addFieldToFilter', 'getItems'])->getMock(); - $visitorCollection->expects($this->atLeastOnce())->method('addFieldToFilter')->willReturnSelf(); - $visitorCollection->expects($this->atLeastOnce())->method('getItems')->willReturn([$visitor, $visitor]); - $this->visitorCollectionFactory->expects($this->atLeastOnce())->method('create') - ->willReturn($visitorCollection); - $this->saveHandler->expects($this->at(0))->method('destroy')->with('session_id_1'); - $this->saveHandler->expects($this->at(1))->method('destroy')->with('session_id_2'); - - $this->assertTrue($this->accountManagement->changePassword($email, $currentPassword, $newPassword)); - } - - public function testResetPassword() - { - $customerEmail = 'customer@example.com'; - $customerId = '1'; - $resetToken = 'newStringToken'; - $newPassword = 'new_password'; - - $this->reInitModel(); - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer->expects($this->any())->method('getId')->willReturn($customerId); - $this->customerRepository->expects($this->atLeastOnce())->method('get')->with($customerEmail) - ->willReturn($customer); - $this->customer->expects($this->atLeastOnce())->method('getResetPasswordLinkExpirationPeriod') - ->willReturn(100000); - $this->string->expects($this->any())->method('strlen')->willReturnCallback( - function ($string) { - return strlen($string); - } - ); - $this->customerRegistry->expects($this->atLeastOnce())->method('retrieveSecureData') - ->willReturn($this->customerSecure); - - $this->customerSecure->expects($this->once()) - ->method('setRpToken') - ->with(null); - $this->customerSecure->expects($this->once()) - ->method('setRpTokenCreatedAt') - ->with(null); - $this->customerSecure->expects($this->any()) - ->method('setPasswordHash') - ->willReturn(null); - - $this->sessionManager->expects($this->atLeastOnce())->method('destroy'); - $this->sessionManager->expects($this->atLeastOnce())->method('getSessionId'); - $visitor = $this->getMockBuilder(\Magento\Customer\Model\Visitor::class) - ->disableOriginalConstructor() - ->setMethods(['getSessionId']) - ->getMock(); - $visitor->expects($this->at(0))->method('getSessionId')->willReturn('session_id_1'); - $visitor->expects($this->at(1))->method('getSessionId')->willReturn('session_id_2'); - $visitorCollection = $this->getMockBuilder( - \Magento\Customer\Model\ResourceModel\Visitor\CollectionFactory::class - ) - ->disableOriginalConstructor()->setMethods(['addFieldToFilter', 'getItems'])->getMock(); - $visitorCollection->expects($this->atLeastOnce())->method('addFieldToFilter')->willReturnSelf(); - $visitorCollection->expects($this->atLeastOnce())->method('getItems')->willReturn([$visitor, $visitor]); - $this->visitorCollectionFactory->expects($this->atLeastOnce())->method('create') - ->willReturn($visitorCollection); - $this->saveHandler->expects($this->at(0))->method('destroy')->with('session_id_1'); - $this->saveHandler->expects($this->at(1))->method('destroy')->with('session_id_2'); - $this->assertTrue($this->accountManagement->resetPassword($customerEmail, $resetToken, $newPassword)); - } - - /** - * @return void - */ - public function testChangePasswordException() - { - $email = 'test@example.com'; - $currentPassword = '1234567'; - $newPassword = 'abcdefg'; - - $exception = new NoSuchEntityException( - new \Magento\Framework\Phrase('Exception message') - ); - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($email) - ->willThrowException($exception); - - $this->expectException( - \Magento\Framework\Exception\InvalidEmailOrPasswordException::class, - 'Invalid login or password.' - ); - - $this->accountManagement->changePassword($email, $currentPassword, $newPassword); - } - - /** - * @return void - */ - public function testAuthenticate() - { - $username = 'login'; - $password = '1234567'; - $passwordHash = '1a2b3f4c'; - - $customerData = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMock(); - - $customerModel = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) - ->disableOriginalConstructor() - ->getMock(); - $customerModel->expects($this->once()) - ->method('updateData') - ->willReturn($customerModel); - - $this->customerRepository - ->expects($this->once()) - ->method('get') - ->with($username) - ->willReturn($customerData); - - $this->authenticationMock->expects($this->once()) - ->method('authenticate'); - - $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn($passwordHash); - - $this->customerRegistry->expects($this->any()) - ->method('retrieveSecureData') - ->willReturn($customerSecure); - - $this->customerFactory->expects($this->once()) - ->method('create') - ->willReturn($customerModel); - - $this->manager->expects($this->exactly(2)) - ->method('dispatch') - ->withConsecutive( - [ - 'customer_customer_authenticated', - ['model' => $customerModel, 'password' => $password] - ], - [ - 'customer_data_object_login', ['customer' => $customerData] - ] - ); - - $this->assertEquals($customerData, $this->accountManagement->authenticate($username, $password)); - } - - /** - * @param int $isConfirmationRequired - * @param string|null $confirmation - * @param string $expected - * @dataProvider dataProviderGetConfirmationStatus - */ - public function testGetConfirmationStatus( - $isConfirmationRequired, - $confirmation, - $expected - ) { - $websiteId = 1; - $customerId = 1; - $customerEmail = 'test1@example.com'; - - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $customerMock->expects($this->once()) - ->method('getId') - ->willReturn($customerId); - $customerMock->expects($this->any()) - ->method('getConfirmation') - ->willReturn($confirmation); - $customerMock->expects($this->once()) - ->method('getEmail') - ->willReturn($customerEmail); - $customerMock->expects($this->once()) - ->method('getWebsiteId') - ->willReturn($websiteId); - - $this->accountConfirmation->expects($this->once()) - ->method('isConfirmationRequired') - ->with($websiteId, $customerId, $customerEmail) - ->willReturn($isConfirmationRequired); - - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customerMock); - - $this->assertEquals($expected, $this->accountManagement->getConfirmationStatus($customerId)); - } - - /** - * @return array - */ - public function dataProviderGetConfirmationStatus() - { - return [ - [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - [0, null, AccountManagement::ACCOUNT_CONFIRMATION_NOT_REQUIRED], - [1, null, AccountManagement::ACCOUNT_CONFIRMED], - [1, 'test', AccountManagement::ACCOUNT_CONFIRMATION_REQUIRED], - ]; - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testCreateAccountWithPasswordHashForGuest() - { - $storeId = 1; - $storeName = 'store_name'; - $websiteId = 1; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->getMock(); - $storeMock->expects($this->once()) - ->method('getId') - ->willReturn($storeId); - $storeMock->expects($this->once()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $storeMock->expects($this->once()) - ->method('getName') - ->willReturn($storeName); - - $this->storeManager->expects($this->exactly(3)) - ->method('getStore') - ->willReturn($storeMock); - - $customerMock = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class) - ->getMockForAbstractClass(); - $customerMock->expects($this->exactly(2)) - ->method('getId') - ->willReturn(null); - $customerMock->expects($this->exactly(3)) - ->method('getStoreId') - ->willReturn(null); - $customerMock->expects($this->exactly(2)) - ->method('getWebsiteId') - ->willReturn(null); - $customerMock->expects($this->once()) - ->method('setStoreId') - ->with($storeId) - ->willReturnSelf(); - $customerMock->expects($this->once()) - ->method('setWebsiteId') - ->with($websiteId) - ->willReturnSelf(); - $customerMock->expects($this->once()) - ->method('setCreatedIn') - ->with($storeName) - ->willReturnSelf(); - $customerMock->expects($this->once()) - ->method('getAddresses') - ->willReturn(null); - $customerMock->expects($this->once()) - ->method('setAddresses') - ->with(null) - ->willReturnSelf(); - - $this->customerRepository - ->expects($this->once()) - ->method('save') - ->with($customerMock, $hash) - ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('Exception message'))); - - $this->accountManagement->createAccountWithPasswordHash($customerMock, $hash); - } - - public function testCreateAccountWithPasswordHashWithCustomerAddresses() - { - $websiteId = 1; - $addressId = 2; - $customerId = null; - $storeId = 1; - $hash = '4nj54lkj5jfi03j49f8bgujfgsd'; - - $this->prepareDateTimeFactory(); - - //Handle store - $store = $this->getMockBuilder(\Magento\Store\Model\Store::class)->disableOriginalConstructor()->getMock(); - $store->expects($this->any()) - ->method('getWebsiteId') - ->willReturn($websiteId); - //Handle address - existing and non-existing. Non-Existing should return null when call getId method - $existingAddress = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $nonExistingAddress = $this->getMockBuilder(\Magento\Customer\Api\Data\AddressInterface::class) - ->disableOriginalConstructor() - ->getMock(); - //Ensure that existing address is not in use - $this->addressRepository - ->expects($this->atLeastOnce()) - ->method("save") - ->withConsecutive( - [$this->logicalNot($this->identicalTo($existingAddress))], - [$this->identicalTo($nonExistingAddress)] - ); - - $existingAddress - ->expects($this->any()) - ->method("getId") - ->willReturn($addressId); - //Expects that id for existing address should be unset - $existingAddress - ->expects($this->once()) - ->method("setId") - ->with(null); - //Handle Customer calls - $customer = $this->getMockBuilder(\Magento\Customer\Api\Data\CustomerInterface::class)->getMock(); - $customer - ->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer - ->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer - ->expects($this->any()) - ->method("getId") - ->willReturn($customerId); - //Return Customer from customer repository - $this->customerRepository - ->expects($this->atLeastOnce()) - ->method('save') - ->willReturn($customer); - $this->customerRepository - ->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customer); - $customerSecure = $this->getMockBuilder(\Magento\Customer\Model\Data\CustomerSecure::class) - ->setMethods(['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash']) - ->disableOriginalConstructor() - ->getMock(); - $customerSecure->expects($this->once()) - ->method('setRpToken') - ->with($hash); - - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn($hash); - - $this->customerRegistry->expects($this->any()) - ->method('retrieveSecureData') - ->with($customerId) - ->willReturn($customerSecure); - - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($hash); - - $customer - ->expects($this->atLeastOnce()) - ->method('getAddresses') - ->willReturn([$existingAddress, $nonExistingAddress]); - - $this->storeManager - ->expects($this->atLeastOnce()) - ->method('getStore') - ->willReturn($store); - - $this->assertSame($customer, $this->accountManagement->createAccountWithPasswordHash($customer, $hash)); - } - - /** - * @return string - */ - private function prepareDateTimeFactory() - { - $dateTime = '2017-10-25 18:57:08'; - $timestamp = '1508983028'; - $dateTimeMock = $this->createMock(\DateTime::class); - $dateTimeMock->expects($this->any()) - ->method('format') - ->with(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT) - ->willReturn($dateTime); - - $dateTimeMock - ->expects($this->any()) - ->method('getTimestamp') - ->willReturn($timestamp); - - $this->dateTimeFactory - ->expects($this->any()) - ->method('create') - ->willReturn($dateTimeMock); - - return $dateTime; - } - - public function testCreateAccountUnexpectedValueException() - { - $websiteId = 1; - $storeId = null; - $defaultStoreId = 1; - $customerId = 1; - $customerEmail = 'email@email.com'; - $newLinkToken = '2jh43j5h2345jh23lh452h345hfuzasd96ofu'; - $exception = new \UnexpectedValueException('Template file was not found'); - - $datetime = $this->prepareDateTimeFactory(); - - $address = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class); - $address->expects($this->once()) - ->method('setCustomerId') - ->with($customerId); - $store = $this->createMock(\Magento\Store\Model\Store::class); - $store->expects($this->once()) - ->method('getId') - ->willReturn($defaultStoreId); - $website = $this->createMock(\Magento\Store\Model\Website::class); - $website->expects($this->atLeastOnce()) - ->method('getStoreIds') - ->willReturn([1, 2, 3]); - $website->expects($this->once()) - ->method('getDefaultStore') - ->willReturn($store); - $customer = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); - $customer->expects($this->atLeastOnce()) - ->method('getId') - ->willReturn($customerId); - $customer->expects($this->atLeastOnce()) - ->method('getEmail') - ->willReturn($customerEmail); - $customer->expects($this->atLeastOnce()) - ->method('getWebsiteId') - ->willReturn($websiteId); - $customer->expects($this->atLeastOnce()) - ->method('getStoreId') - ->willReturn($storeId); - $customer->expects($this->once()) - ->method('setStoreId') - ->with($defaultStoreId); - $customer->expects($this->once()) - ->method('getAddresses') - ->willReturn([$address]); - $customer->expects($this->once()) - ->method('setAddresses') - ->with(null); - $this->customerRepository->expects($this->once()) - ->method('get') - ->with($customerEmail) - ->willReturn($customer); - $this->share->expects($this->once()) - ->method('isWebsiteScope') - ->willReturn(true); - $this->storeManager->expects($this->atLeastOnce()) - ->method('getWebsite') - ->with($websiteId) - ->willReturn($website); - $this->customerRepository->expects($this->atLeastOnce()) - ->method('save') - ->willReturn($customer); - $this->addressRepository->expects($this->atLeastOnce()) - ->method('save') - ->with($address); - $this->customerRepository->expects($this->once()) - ->method('getById') - ->with($customerId) - ->willReturn($customer); - $this->random->expects($this->once()) - ->method('getUniqueHash') - ->willReturn($newLinkToken); - $customerSecure = $this->createPartialMock( - \Magento\Customer\Model\Data\CustomerSecure::class, - ['setRpToken', 'setRpTokenCreatedAt', 'getPasswordHash'] - ); - $customerSecure->expects($this->any()) - ->method('setRpToken') - ->with($newLinkToken); - $customerSecure->expects($this->any()) - ->method('setRpTokenCreatedAt') - ->with($datetime) - ->willReturnSelf(); - $customerSecure->expects($this->any()) - ->method('getPasswordHash') - ->willReturn(null); - $this->customerRegistry->expects($this->atLeastOnce()) - ->method('retrieveSecureData') - ->willReturn($customerSecure); - $this->emailNotificationMock->expects($this->once()) - ->method('newAccount') - ->willThrowException($exception); - $this->logger->expects($this->once())->method('error')->with($exception); - - $this->accountManagement->createAccount($customer); - } -} diff --git a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php index f5b7f08d2906d..b9e73a6613bab 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/CustomerTest.php @@ -307,8 +307,6 @@ public function testUpdateData() } $expectedResult[$attribute->getAttributeCode()] = $attribute->getValue(); - $expectedResult['attribute_set_id'] = - \Magento\Customer\Api\CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER; $this->assertEquals($this->_model->getData(), $expectedResult); } diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php index 41816d26982d6..215cc32193b18 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/CustomerRepositoryTest.php @@ -334,12 +334,6 @@ public function testSave() $customerModel->expects($this->once()) ->method('setId') ->with($customerId); - $customerModel->expects($this->once()) - ->method('getAttributeSetId') - ->willReturn(null); - $customerModel->expects($this->once()) - ->method('setAttributeSetId') - ->with(\Magento\Customer\Api\CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER); $customerAttributesMetaData->expects($this->atLeastOnce()) ->method('getId') ->willReturn($customerId); @@ -616,12 +610,6 @@ public function testSaveWithPasswordHash() $customerModel->expects($this->once()) ->method('setId') ->with($customerId); - $customerModel->expects($this->once()) - ->method('getAttributeSetId') - ->willReturn(null); - $customerModel->expects($this->once()) - ->method('setAttributeSetId') - ->with(\Magento\Customer\Api\CustomerMetadataInterface::ATTRIBUTE_SET_ID_CUSTOMER); $customerModel->expects($this->atLeastOnce()) ->method('getId') ->willReturn($customerId); diff --git a/app/code/Magento/Customer/composer.json b/app/code/Magento/Customer/composer.json index 65cb1e257f5b6..e3d8f22088ef6 100644 --- a/app/code/Magento/Customer/composer.json +++ b/app/code/Magento/Customer/composer.json @@ -29,7 +29,7 @@ "magento/module-customer-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "101.0.5", + "version": "101.0.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html b/app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html index 6c17762a88227..59e7f16adfd51 100644 --- a/app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html +++ b/app/code/Magento/Customer/view/frontend/email/password_reset_confirmation.html @@ -21,7 +21,7 @@ <table class="inner-wrapper" border="0" cellspacing="0" cellpadding="0" align="center"> <tr> <td align="center"> - <a href="{{var this.getUrl($store,'customer/account/createPassword/',[_query:[id:$customer.id,token:$customer.rp_token],_nosid:1])}}" target="_blank">{{trans "Set a New Password"}}</a> + <a href="{{var this.getUrl($store,'customer/account/createPassword/',[_query:[token:$customer.rp_token],_nosid:1])}}" target="_blank">{{trans "Set a New Password"}}</a> </td> </tr> </table> diff --git a/app/code/Magento/Customer/view/frontend/templates/form/resetforgottenpassword.phtml b/app/code/Magento/Customer/view/frontend/templates/form/resetforgottenpassword.phtml index 15e570da04beb..e79cea80ac838 100644 --- a/app/code/Magento/Customer/view/frontend/templates/form/resetforgottenpassword.phtml +++ b/app/code/Magento/Customer/view/frontend/templates/form/resetforgottenpassword.phtml @@ -8,7 +8,7 @@ /** @var \Magento\Customer\Block\Account\Resetpassword $block */ ?> -<form action="<?= $block->escapeUrl($block->getUrl('*/*/resetpasswordpost', ['_query' => ['id' => $block->getCustomerId(), 'token' => $block->getResetPasswordLinkToken()]])) ?>" +<form action="<?= $block->escapeUrl($block->getUrl('*/*/resetpasswordpost', ['_query' => ['token' => $block->getResetPasswordLinkToken()]])) ?>" method="post" <?php if ($block->isAutocompleteDisabled()) :?> autocomplete="off"<?php endif; ?> id="form-validate" diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js index ec21e45da6852..39e3f8d95ee3b 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js +++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js @@ -75,17 +75,17 @@ define([ /** * @param {Object} sectionNames - * @param {Number} updateSectionId + * @param {Boolean} forceNewSectionTimestamp * @return {*} */ - getFromServer: function (sectionNames, updateSectionId) { + getFromServer: function (sectionNames, forceNewSectionTimestamp) { var parameters; sectionNames = sectionConfig.filterClientSideSections(sectionNames); parameters = _.isArray(sectionNames) ? { sections: sectionNames.join(',') } : []; - parameters['update_section_id'] = updateSectionId; + parameters['force_new_section_timestamp'] = forceNewSectionTimestamp; return $.getJSON(options.sectionLoadUrl, parameters).fail(function (jqXHR) { throw new Error(jqXHR); @@ -199,7 +199,8 @@ define([ privateContent = $.cookieStorage.get(privateContentVersion), localPrivateContent = $.localStorage.get(privateContentVersion), needVersion = 'need_version', - expiredSectionNames = this.getExpiredSectionNames(); + expiredSectionNames = this.getExpiredSectionNames(), + isLoading = false; if (privateContent && !$.cookieStorage.isSet(privateContentVersion) && @@ -208,6 +209,7 @@ define([ $.cookieStorage.set(privateContentVersion, needVersion); $.localStorage.set(privateContentVersion, needVersion); this.reload([], false); + isLoading = true; } else if (localPrivateContent !== privateContent) { if (!$.cookieStorage.isSet(privateContentVersion)) { privateContent = needVersion; @@ -215,6 +217,7 @@ define([ } $.localStorage.set(privateContentVersion, privateContent); this.reload([], false); + isLoading = true; } else if (expiredSectionNames.length > 0) { _.each(dataProvider.getFromStorage(storage.keys()), function (sectionData, sectionName) { buffer.notify(sectionName, sectionData); @@ -233,7 +236,7 @@ define([ if (!_.isEmpty(privateContent)) { countryData = this.get('directory-data'); - if (_.isEmpty(countryData())) { + if (_.isEmpty(countryData()) && !isLoading) { customerData.reload(['directory-data'], false); } } @@ -323,11 +326,11 @@ define([ /** * @param {Array} sectionNames - * @param {Number} updateSectionId + * @param {Boolean} forceNewSectionTimestamp * @return {*} */ - reload: function (sectionNames, updateSectionId) { - return dataProvider.getFromServer(sectionNames, updateSectionId).done(function (sections) { + reload: function (sectionNames, forceNewSectionTimestamp) { + return dataProvider.getFromServer(sectionNames, forceNewSectionTimestamp).done(function (sections) { $(document).trigger('customer-data-reload', [sectionNames]); buffer.update(sections); }); diff --git a/app/code/Magento/CustomerAnalytics/Test/Mftf/composer.json b/app/code/Magento/CustomerAnalytics/Test/Mftf/composer.json deleted file mode 100644 index da8746419f509..0000000000000 --- a/app/code/Magento/CustomerAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-customer-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-customer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CustomerAnalytics/composer.json b/app/code/Magento/CustomerAnalytics/composer.json index 3ce37a3c881ec..d9c428ffcf5b7 100644 --- a/app/code/Magento/CustomerAnalytics/composer.json +++ b/app/code/Magento/CustomerAnalytics/composer.json @@ -7,7 +7,7 @@ "magento/module-customer": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/CustomerImportExport/Model/Export/Customer.php b/app/code/Magento/CustomerImportExport/Model/Export/Customer.php index 14f0ae324e0a4..9ea9e1e5bd93d 100644 --- a/app/code/Magento/CustomerImportExport/Model/Export/Customer.php +++ b/app/code/Magento/CustomerImportExport/Model/Export/Customer.php @@ -15,7 +15,7 @@ */ class Customer extends \Magento\ImportExport\Model\Export\Entity\AbstractEav { - /**#@+ + /** * Permanent column names. * * Names that begins with underscore is not an attribute. This name convention is for @@ -27,23 +27,19 @@ class Customer extends \Magento\ImportExport\Model\Export\Entity\AbstractEav const COLUMN_STORE = '_store'; - /**#@-*/ - - /**#@+ + /** * Attribute collection name */ const ATTRIBUTE_COLLECTION_NAME = \Magento\Customer\Model\ResourceModel\Attribute\Collection::class; - /**#@-*/ - - /**#@+ + /** * XML path to page size parameter */ const XML_PATH_PAGE_SIZE = 'export/customer_page_size/customer'; - /**#@-*/ - - /**#@-*/ + /** + * @var array + */ protected $_attributeOverrides = [ 'created_at' => ['backend_type' => 'datetime'], 'reward_update_notification' => ['source_model' => \Magento\Eav\Model\Entity\Attribute\Source\Boolean::class], diff --git a/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php b/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php index 36c1e761d013c..1ff1adc3dac58 100644 --- a/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php +++ b/app/code/Magento/CustomerImportExport/Model/Import/CustomerComposite.php @@ -299,8 +299,8 @@ public function validateData() $rows = []; foreach ($source as $row) { $rows[] = [ - Address::COLUMN_EMAIL => $row[Customer::COLUMN_EMAIL], - Address::COLUMN_WEBSITE => $row[Customer::COLUMN_WEBSITE] + Address::COLUMN_EMAIL => $row[Customer::COLUMN_EMAIL] ?? null, + Address::COLUMN_WEBSITE => $row[Customer::COLUMN_WEBSITE] ?? null ]; } $source->rewind(); diff --git a/app/code/Magento/CustomerImportExport/Test/Mftf/composer.json b/app/code/Magento/CustomerImportExport/Test/Mftf/composer.json deleted file mode 100644 index 92d9ecbe01763..0000000000000 --- a/app/code/Magento/CustomerImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-customer-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/CustomerImportExport/composer.json b/app/code/Magento/CustomerImportExport/composer.json index 1bf1993ff8602..d71be31723043 100644 --- a/app/code/Magento/CustomerImportExport/composer.json +++ b/app/code/Magento/CustomerImportExport/composer.json @@ -12,7 +12,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php b/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php index 9a987c704dea0..4ab6bdf4565a2 100644 --- a/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php +++ b/app/code/Magento/Deploy/Model/DeploymentConfig/ImporterPool.php @@ -184,14 +184,7 @@ public function getValidator($section) private function sort(array $data) { uasort($data, function (array $a, array $b) { - $a['sort_order'] = $this->getSortOrder($a); - $b['sort_order'] = $this->getSortOrder($b); - - if ($a['sort_order'] == $b['sort_order']) { - return 0; - } - - return ($a['sort_order'] < $b['sort_order']) ? -1 : 1; + return $this->getSortOrder($a) <=> $this->getSortOrder($b); }); return $data; diff --git a/app/code/Magento/Deploy/Test/Mftf/composer.json b/app/code/Magento/Deploy/Test/Mftf/composer.json deleted file mode 100644 index 54cea8bffb5d8..0000000000000 --- a/app/code/Magento/Deploy/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-deploy", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-require-js": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Deploy/composer.json b/app/code/Magento/Deploy/composer.json index 829dda9dea8e7..3c28dc345cba4 100644 --- a/app/code/Magento/Deploy/composer.json +++ b/app/code/Magento/Deploy/composer.json @@ -10,7 +10,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php b/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php index 25519e5c83054..a2db0f43061ea 100644 --- a/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php +++ b/app/code/Magento/Developer/Console/Command/SourceThemeDeployCommand.php @@ -5,7 +5,6 @@ */ namespace Magento\Developer\Console\Command; -use Magento\Framework\App\State; use Magento\Framework\Validator\Locale; use Magento\Framework\View\Asset\Repository; use Symfony\Component\Console\Command\Command; diff --git a/app/code/Magento/Developer/Test/Mftf/composer.json b/app/code/Magento/Developer/Test/Mftf/composer.json deleted file mode 100644 index 5fa9f01bc384d..0000000000000 --- a/app/code/Magento/Developer/Test/Mftf/composer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "magento/functional-test-module-developer", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Developer/composer.json b/app/code/Magento/Developer/composer.json index 0a8853064968d..e2236e248cfc3 100644 --- a/app/code/Magento/Developer/composer.json +++ b/app/code/Magento/Developer/composer.json @@ -8,7 +8,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Developer/etc/di.xml b/app/code/Magento/Developer/etc/di.xml index 85b28c90132af..9e8944d741afe 100644 --- a/app/code/Magento/Developer/etc/di.xml +++ b/app/code/Magento/Developer/etc/di.xml @@ -237,4 +237,10 @@ </argument> </arguments> </type> + + <type name="Magento\Developer\Model\TemplateEngine\Plugin\DebugHints"> + <arguments> + <argument name="debugHintsPath" xsi:type="string">dev/debug/template_hints_storefront</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Developer/etc/frontend/di.xml b/app/code/Magento/Developer/etc/frontend/di.xml index 4c65510388eb3..aa4b347260209 100644 --- a/app/code/Magento/Developer/etc/frontend/di.xml +++ b/app/code/Magento/Developer/etc/frontend/di.xml @@ -9,9 +9,4 @@ <type name="Magento\Framework\View\TemplateEngineFactory"> <plugin name="debug_hints" type="Magento\Developer\Model\TemplateEngine\Plugin\DebugHints" sortOrder="10"/> </type> - <type name="Magento\Developer\Model\TemplateEngine\Plugin\DebugHints"> - <arguments> - <argument name="debugHintsPath" xsi:type="string">dev/debug/template_hints_storefront</argument> - </arguments> - </type> </config> diff --git a/app/code/Magento/Dhl/Test/Mftf/composer.json b/app/code/Magento/Dhl/Test/Mftf/composer.json deleted file mode 100644 index 8aa7b6e518c8a..0000000000000 --- a/app/code/Magento/Dhl/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-dhl", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-checkout": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Dhl/composer.json b/app/code/Magento/Dhl/composer.json index 47bdcff4d6be9..fae1eb9e1454f 100644 --- a/app/code/Magento/Dhl/composer.json +++ b/app/code/Magento/Dhl/composer.json @@ -19,7 +19,7 @@ "magento/module-checkout": "100.2.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Directory/Block/Data.php b/app/code/Magento/Directory/Block/Data.php index ca2b4b95b5574..630c0966bc2f9 100644 --- a/app/code/Magento/Directory/Block/Data.php +++ b/app/code/Magento/Directory/Block/Data.php @@ -184,7 +184,7 @@ public function getRegionHtmlSelect() )->setClass( 'required-entry validate-state' )->setValue( - intval($this->getRegionId()) + (int)$this->getRegionId() )->setOptions( $options )->getHtml(); diff --git a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php index eb2b7cef0b707..da5ad64f8b239 100644 --- a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php +++ b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php @@ -349,6 +349,8 @@ public function setForegroundCountries($foregroundCountries) } /** + * @deprecated use \Magento\Directory\Helper\Data::getCountriesWithStatesRequired() instead + * * Get list of countries with required states * * @return \Magento\Directory\Model\Country[] diff --git a/app/code/Magento/Directory/Setup/UpgradeData.php b/app/code/Magento/Directory/Setup/UpgradeData.php index 4ee9ea33673d7..8ef73e2ef36b0 100644 --- a/app/code/Magento/Directory/Setup/UpgradeData.php +++ b/app/code/Magento/Directory/Setup/UpgradeData.php @@ -32,7 +32,7 @@ public function __construct(Data $directoryData) } /** - * Upgrades data for Directry module. + * Upgrades data for Directory module. * * @param ModuleDataSetupInterface $setup * @param ModuleContextInterface $context @@ -41,10 +41,13 @@ public function __construct(Data $directoryData) public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { if (version_compare($context->getVersion(), '2.0.1', '<')) { - $this->addCountryRegions($setup, $this->getDataForCroatia()); + $this->addCountryRegions($setup, 'HR', $this->getDataForCroatia()); } if (version_compare($context->getVersion(), '2.0.2', '<')) { - $this->addCountryRegions($setup, $this->getDataForIndia()); + $this->addCountryRegions($setup, 'IN', $this->getDataForIndia()); + } + if (version_compare($context->getVersion(), '2.0.3', '<')) { + $this->addCountryRegions($setup, 'AU', $this->getDataForAustralia()); } } @@ -56,27 +59,27 @@ public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface private function getDataForCroatia() { return [ - ['HR', 'HR-01', 'Zagrebačka županija'], - ['HR', 'HR-02', 'Krapinsko-zagorska županija'], - ['HR', 'HR-03', 'Sisačko-moslavačka županija'], - ['HR', 'HR-04', 'Karlovačka županija'], - ['HR', 'HR-05', 'Varaždinska županija'], - ['HR', 'HR-06', 'Koprivničko-križevačka županija'], - ['HR', 'HR-07', 'Bjelovarsko-bilogorska županija'], - ['HR', 'HR-08', 'Primorsko-goranska županija'], - ['HR', 'HR-09', 'Ličko-senjska županija'], - ['HR', 'HR-10', 'Virovitičko-podravska županija'], - ['HR', 'HR-11', 'Požeško-slavonska županija'], - ['HR', 'HR-12', 'Brodsko-posavska županija'], - ['HR', 'HR-13', 'Zadarska županija'], - ['HR', 'HR-14', 'Osječko-baranjska županija'], - ['HR', 'HR-15', 'Šibensko-kninska županija'], - ['HR', 'HR-16', 'Vukovarsko-srijemska županija'], - ['HR', 'HR-17', 'Splitsko-dalmatinska županija'], - ['HR', 'HR-18', 'Istarska županija'], - ['HR', 'HR-19', 'Dubrovačko-neretvanska županija'], - ['HR', 'HR-20', 'Međimurska županija'], - ['HR', 'HR-21', 'Grad Zagreb'] + 'HR-01' => 'Zagrebačka županija', + 'HR-02' => 'Krapinsko-zagorska županija', + 'HR-03' => 'Sisačko-moslavačka županija', + 'HR-04' => 'Karlovačka županija', + 'HR-05' => 'Varaždinska županija', + 'HR-06' => 'Koprivničko-križevačka županija', + 'HR-07' => 'Bjelovarsko-bilogorska županija', + 'HR-08' => 'Primorsko-goranska županija', + 'HR-09' => 'Ličko-senjska županija', + 'HR-10' => 'Virovitičko-podravska županija', + 'HR-11' => 'Požeško-slavonska županija', + 'HR-12' => 'Brodsko-posavska županija', + 'HR-13' => 'Zadarska županija', + 'HR-14' => 'Osječko-baranjska županija', + 'HR-15' => 'Šibensko-kninska županija', + 'HR-16' => 'Vukovarsko-srijemska županija', + 'HR-17' => 'Splitsko-dalmatinska županija', + 'HR-18' => 'Istarska županija', + 'HR-19' => 'Dubrovačko-neretvanska županija', + 'HR-20' => 'Međimurska županija', + 'HR-21' => 'Grad Zagreb', ]; } @@ -88,42 +91,61 @@ private function getDataForCroatia() private function getDataForIndia() { return [ - ['IN', 'AN', 'Andaman and Nicobar Islands'], - ['IN', 'AP', 'Andhra Pradesh'], - ['IN', 'AR', 'Arunachal Pradesh'], - ['IN', 'AS', 'Assam'], - ['IN', 'BR', 'Bihar'], - ['IN', 'CH', 'Chandigarh'], - ['IN', 'CT', 'Chhattisgarh'], - ['IN', 'DN', 'Dadra and Nagar Haveli'], - ['IN', 'DD', 'Daman and Diu'], - ['IN', 'DL', 'Delhi'], - ['IN', 'GA', 'Goa'], - ['IN', 'GJ', 'Gujarat'], - ['IN', 'HR', 'Haryana'], - ['IN', 'HP', 'Himachal Pradesh'], - ['IN', 'JK', 'Jammu and Kashmir'], - ['IN', 'JH', 'Jharkhand'], - ['IN', 'KA', 'Karnataka'], - ['IN', 'KL', 'Kerala'], - ['IN', 'LD', 'Lakshadweep'], - ['IN', 'MP', 'Madhya Pradesh'], - ['IN', 'MH', 'Maharashtra'], - ['IN', 'MN', 'Manipur'], - ['IN', 'ML', 'Meghalaya'], - ['IN', 'MZ', 'Mizoram'], - ['IN', 'NL', 'Nagaland'], - ['IN', 'OR', 'Odisha'], - ['IN', 'PY', 'Puducherry'], - ['IN', 'PB', 'Punjab'], - ['IN', 'RJ', 'Rajasthan'], - ['IN', 'SK', 'Sikkim'], - ['IN', 'TN', 'Tamil Nadu'], - ['IN', 'TG', 'Telangana'], - ['IN', 'TR', 'Tripura'], - ['IN', 'UP', 'Uttar Pradesh'], - ['IN', 'UT', 'Uttarakhand'], - ['IN', 'WB', 'West Bengal'] + 'AN' => 'Andaman and Nicobar Islands', + 'AP' => 'Andhra Pradesh', + 'AR' => 'Arunachal Pradesh', + 'AS' => 'Assam', + 'BR' => 'Bihar', + 'CH' => 'Chandigarh', + 'CT' => 'Chhattisgarh', + 'DN' => 'Dadra and Nagar Haveli', + 'DD' => 'Daman and Diu', + 'DL' => 'Delhi', + 'GA' => 'Goa', + 'GJ' => 'Gujarat', + 'HR' => 'Haryana', + 'HP' => 'Himachal Pradesh', + 'JK' => 'Jammu and Kashmir', + 'JH' => 'Jharkhand', + 'KA' => 'Karnataka', + 'KL' => 'Kerala', + 'LD' => 'Lakshadweep', + 'MP' => 'Madhya Pradesh', + 'MH' => 'Maharashtra', + 'MN' => 'Manipur', + 'ML' => 'Meghalaya', + 'MZ' => 'Mizoram', + 'NL' => 'Nagaland', + 'OR' => 'Odisha', + 'PY' => 'Puducherry', + 'PB' => 'Punjab', + 'RJ' => 'Rajasthan', + 'SK' => 'Sikkim', + 'TN' => 'Tamil Nadu', + 'TG' => 'Telangana', + 'TR' => 'Tripura', + 'UP' => 'Uttar Pradesh', + 'UT' => 'Uttarakhand', + 'WB' => 'West Bengal', + ]; + } + + /** + * Australian states data. + * + * @return array + */ + private function getDataForAustralia() + { + return [ + 'ACT' => 'Australian Capital Territory', + 'NSW' => 'New South Wales', + 'VIC' => 'Victoria', + 'QLD' => 'Queensland', + 'SA' => 'South Australia', + 'TAS' => 'Tasmania', + 'WA' => 'Western Australia', + 'NT' => 'Northern Territory' ]; } @@ -131,30 +153,31 @@ private function getDataForIndia() * Add country regions data to appropriate tables. * * @param ModuleDataSetupInterface $setup + * @param string $countryId * @param array $data * @return void */ - private function addCountryRegions(ModuleDataSetupInterface $setup, array $data) + private function addCountryRegions(ModuleDataSetupInterface $setup, string $countryId, array $data) { /** * Fill table directory/country_region * Fill table directory/country_region_name for en_US locale */ - foreach ($data as $row) { - $bind = ['country_id' => $row[0], 'code' => $row[1], 'default_name' => $row[2]]; + foreach ($data as $code => $name) { + $bind = ['country_id' => $countryId, 'code' => $code, 'default_name' => $name]; $setup->getConnection()->insert($setup->getTable('directory_country_region'), $bind); $regionId = $setup->getConnection()->lastInsertId($setup->getTable('directory_country_region')); - $bind = ['locale' => 'en_US', 'region_id' => $regionId, 'name' => $row[2]]; + $bind = ['locale' => 'en_US', 'region_id' => $regionId, 'name' => $name]; $setup->getConnection()->insert($setup->getTable('directory_country_region_name'), $bind); } + /** * Upgrade core_config_data general/region/state_required field. */ - $countries = $this->directoryData->getCountryCollection()->getCountriesWithRequiredStates(); $setup->getConnection()->update( $setup->getTable('core_config_data'), [ - 'value' => implode(',', array_keys($countries)) + 'value' => new \Zend_Db_Expr("CONCAT(value, '," . $countryId . "')") ], [ 'scope="default"', diff --git a/app/code/Magento/Directory/Test/Mftf/composer.json b/app/code/Magento/Directory/Test/Mftf/composer.json deleted file mode 100644 index d73ebccc5d5eb..0000000000000 --- a/app/code/Magento/Directory/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-directory", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Directory/composer.json b/app/code/Magento/Directory/composer.json index c2749673cb062..c6de1d1eefe6c 100644 --- a/app/code/Magento/Directory/composer.json +++ b/app/code/Magento/Directory/composer.json @@ -10,7 +10,7 @@ "lib-libxml": "*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Directory/etc/module.xml b/app/code/Magento/Directory/etc/module.xml index a3735ca6ddde1..cc8023b9b3873 100644 --- a/app/code/Magento/Directory/etc/module.xml +++ b/app/code/Magento/Directory/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Directory" setup_version="2.0.2"> + <module name="Magento_Directory" setup_version="2.0.3"> <sequence> <module name="Magento_Store"/> </sequence> diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php index 38444de78ab9c..284a9c925ed40 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/File/Upload.php @@ -96,14 +96,6 @@ public function execute() $relativePath = rtrim($tmpPath, '/') . '/' . ltrim($result['file'], '/'); $this->storageDatabase->saveFile($relativePath); } - - $result['cookie'] = [ - 'name' => $this->_getSession()->getName(), - 'value' => $this->_getSession()->getSessionId(), - 'lifetime' => $this->_getSession()->getCookieLifetime(), - 'path' => $this->_getSession()->getCookiePath(), - 'domain' => $this->_getSession()->getCookieDomain(), - ]; } catch (\Exception $e) { $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; } diff --git a/app/code/Magento/Downloadable/Setup/UpgradeData.php b/app/code/Magento/Downloadable/Setup/UpgradeData.php new file mode 100644 index 0000000000000..aec15ce7f9d90 --- /dev/null +++ b/app/code/Magento/Downloadable/Setup/UpgradeData.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Downloadable\Setup; + +use Magento\Eav\Setup\EavSetup; +use Magento\Eav\Setup\EavSetupFactory; +use Magento\Framework\Setup\ModuleDataSetupInterface; +use Magento\Framework\Setup\UpgradeDataInterface; +use Magento\Framework\Setup\ModuleContextInterface; + +/** + * @codeCoverageIgnore + */ +class UpgradeData implements UpgradeDataInterface +{ + /** + * EAV setup factory + * + * @var EavSetupFactory + */ + private $eavSetupFactory; + + /** + * Init + * + * @param EavSetupFactory $eavSetupFactory + */ + public function __construct(EavSetupFactory $eavSetupFactory) + { + $this->eavSetupFactory = $eavSetupFactory; + } + + /** + * @inheritdoc + */ + public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.3', '<')) { + /** @var EavSetup $eavSetup */ + $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); + // remove default value + $eavSetup->updateAttribute( + \Magento\Catalog\Model\Product::ENTITY, + 'links_exist', + 'default_value', + null + ); + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml new file mode 100644 index 0000000000000..4f48ca7309baa --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/LinkData.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ApiDownloadableLink" type="downloadable_link"> + <data key="title" unique="suffix">Api Downloadable Link</data> + <data key="price">2.00</data> + <data key="link_type">url</data> + <data key="shareable">No</data> + <data key="number_of_downloads">1000</data> + <data key="sort_order">0</data> + <data key="link_url">https://static.magento.com/sites/all/themes/mag_redesign/images/magento-logo.svg</data> + </entity> +</entities> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml new file mode 100644 index 0000000000000..28b4aa261b941 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Data/ProductData.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ApiDownloadableProduct" type="product"> + <data key="sku" unique="suffix">api-downloadable-product</data> + <data key="type_id">downloadable</data> + <data key="attribute_set_id">4</data> + <data key="visibility">4</data> + <data key="name" unique="suffix">Api Downloadable Product</data> + <data key="price">123.00</data> + <data key="urlKey" unique="suffix">api-downloadable-product</data> + <data key="status">1</data> + <data key="quantity">100</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductShortDescription</requiredEntity> + <requiredEntity type="downloadable_link">apiDownloadableLink</requiredEntity> + </entity> +</entities> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml new file mode 100644 index 0000000000000..2511244d445c1 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/downloadable_link-meta.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateDownloadableLink" dataType="downloadable_link" type="create" auth="adminOauth" url="/V1/products/{sku}/downloadable-links" method="POST"> + <contentType>application/json</contentType> + <object dataType="downloadable_link" key="link"> + <field key="title">string</field> + <field key="sort_order">integer</field> + <field key="is_shareable">integer</field> + <field key="price">number</field> + <field key="number_of_downloads">integer</field> + <field key="link_type">string</field> + <field key="link_file">string</field> + <field key="link_file_content">link_file_content</field> + <field key="file_data">string</field> + <field key="link_url">string</field> + <field key="sample_type">string</field> + <field key="sample_file">string</field> + <field key="sample_file_content">sample_file_content</field> + <field key="sample_url">string</field> + </object> + <field key="isGlobalScopeContent">boolean</field> + </operation> +</operations> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml new file mode 100644 index 0000000000000..d5d6c16c71736 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/link_file_content-meta.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateLinkFileContent" dataType="link_file_content" type="create"> + <field key="file_data">string</field> + <field key="name">string</field> + </operation> +</operations> diff --git a/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml b/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml new file mode 100644 index 0000000000000..3da91807ceb48 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Metadata/sample_file_content-meta.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateSampleFileContent" dataType="sample_file_content" type="create"> + <field key="file_data">string</field> + <field key="name">string</field> + </operation> +</operations> diff --git a/app/code/Magento/Downloadable/Test/Mftf/composer.json b/app/code/Magento/Downloadable/Test/Mftf/composer.json deleted file mode 100644 index b9cfde1fff61b..0000000000000 --- a/app/code/Magento/Downloadable/Test/Mftf/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "magento/functional-test-module-downloadable", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-gift-message": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-downloadable-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/File/UploadTest.php b/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/File/UploadTest.php index df1f127fae313..281fb571ff56f 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/File/UploadTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/File/UploadTest.php @@ -59,11 +59,6 @@ class UploadTest extends \PHPUnit\Framework\TestCase */ protected $fileHelper; - /** - * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Backend\Model\Session - */ - protected $session; - /** * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Controller\ResultFactory */ @@ -81,9 +76,6 @@ protected function setUp() ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->session = $this->getMockBuilder(\Magento\Backend\Model\Session::class) - ->disableOriginalConstructor() - ->getMock(); $this->resultFactory = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) @@ -108,9 +100,6 @@ protected function setUp() $this->context->expects($this->any()) ->method('getRequest') ->will($this->returnValue($this->request)); - $this->context->expects($this->any()) - ->method('getSession') - ->will($this->returnValue($this->session)); $this->context->expects($this->any()) ->method('getResultFactory') ->will($this->returnValue($this->resultFactory)); @@ -154,11 +143,6 @@ public function testExecute() $this->uploaderFactory->expects($this->once())->method('create')->willReturn($uploader); $this->fileHelper->expects($this->once())->method('uploadFromTmp')->willReturn($data); $this->storageDatabase->expects($this->once())->method('saveFile'); - $this->session->expects($this->once())->method('getName')->willReturn('Name'); - $this->session->expects($this->once())->method('getSessionId')->willReturn('SessionId'); - $this->session->expects($this->once())->method('getCookieLifetime')->willReturn('CookieLifetime'); - $this->session->expects($this->once())->method('getCookiePath')->willReturn('CookiePath'); - $this->session->expects($this->once())->method('getCookieDomain')->willReturn('CookieDomain'); $this->resultFactory->expects($this->once())->method('create')->willReturn($resultJson); $resultJson->expects($this->once())->method('setData')->willReturnSelf(); diff --git a/app/code/Magento/Downloadable/composer.json b/app/code/Magento/Downloadable/composer.json index 9be657435161b..e737c3121f454 100644 --- a/app/code/Magento/Downloadable/composer.json +++ b/app/code/Magento/Downloadable/composer.json @@ -25,7 +25,7 @@ "magento/module-downloadable-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Downloadable/etc/module.xml b/app/code/Magento/Downloadable/etc/module.xml index 4c4e165feb014..fb13121335730 100644 --- a/app/code/Magento/Downloadable/etc/module.xml +++ b/app/code/Magento/Downloadable/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Downloadable" setup_version="2.0.2"> + <module name="Magento_Downloadable" setup_version="2.0.3"> <sequence> <module name="Magento_Catalog"/> </sequence> diff --git a/app/code/Magento/DownloadableImportExport/Test/Mftf/composer.json b/app/code/Magento/DownloadableImportExport/Test/Mftf/composer.json deleted file mode 100644 index b6c26567a95d1..0000000000000 --- a/app/code/Magento/DownloadableImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-downloadable-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/DownloadableImportExport/composer.json b/app/code/Magento/DownloadableImportExport/composer.json index a91c87f60c065..763eceaea8460 100644 --- a/app/code/Magento/DownloadableImportExport/composer.json +++ b/app/code/Magento/DownloadableImportExport/composer.json @@ -12,7 +12,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php index 0f3a1f2220644..1f13fe1405273 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/AbstractAttribute.php @@ -392,7 +392,7 @@ public function getIsVisibleOnFront() } /** - * @return string|int|bool|float + * @return string|null * @codeCoverageIgnore */ public function getDefaultValue() @@ -594,9 +594,11 @@ public function getSource() { if (empty($this->_source)) { if (!$this->getSourceModel()) { - $this->setSourceModel($this->_getDefaultSourceModel()); + $this->_source = $this->_getDefaultSourceModel(); + } else { + $this->_source = $this->getSourceModel(); } - $source = $this->_universalFactory->create($this->getSourceModel()); + $source = $this->_universalFactory->create($this->_source); if (!$source) { throw new LocalizedException( __( diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php index 38e7b883f4ea5..e4bf1892d7222 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Backend/AbstractBackend.php @@ -203,12 +203,12 @@ public function getEntityValueId($entity) /** * Retrieve default value * - * @return mixed + * @return string */ public function getDefaultValue() { if ($this->_defaultValue === null) { - if ($this->getAttribute()->getDefaultValue()) { + if ($this->getAttribute()->getDefaultValue() !== null) { $this->_defaultValue = $this->getAttribute()->getDefaultValue(); } else { $this->_defaultValue = ""; @@ -280,7 +280,7 @@ public function afterLoad($object) public function beforeSave($object) { $attrCode = $this->getAttribute()->getAttributeCode(); - if (!$object->hasData($attrCode) && $this->getDefaultValue()) { + if (!$object->hasData($attrCode) && $this->getDefaultValue() !== '') { $object->setData($attrCode, $this->getDefaultValue()); } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php index b0508fd8cc626..01bdddc32af90 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/OptionManagement.php @@ -145,6 +145,6 @@ protected function validateOption($attribute, $optionId) */ private function getOptionId($option) { - return $option->getValue() ?: 'new_option'; + return 'id_' . ($option->getValue() ?: 'new_option'); } } diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php b/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php index 0f3b8be32b988..59164a1dd0922 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php @@ -10,6 +10,8 @@ use Magento\Framework\Escaper; /** + * Eav attribute default source when values are coming from another table + * * @api * @since 100.0.2 */ @@ -136,12 +138,14 @@ public function getSpecificOptions($ids, $withEmpty = true) } /** + * Add an empty option to the array + * * @param array $options * @return array */ private function addEmptyOption(array $options) { - array_unshift($options, ['label' => $this->getAttribute()->getIsRequired() ? '' : ' ', 'value' => '']); + array_unshift($options, ['label' => ' ', 'value' => '']); return $options; } diff --git a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php index d61166e5c71a9..2177686a4a6be 100644 --- a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php +++ b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php @@ -374,6 +374,7 @@ public function addAttributeToFilter($attribute, $condition = null, $joinType = if (!empty($conditionSql)) { $this->getSelect()->where($conditionSql, null, \Magento\Framework\DB\Select::TYPE_CONDITION); + $this->invalidateSize(); } else { throw new \Magento\Framework\Exception\LocalizedException( __('Invalid attribute identifier for filter (%1)', get_class($attribute)) @@ -1232,7 +1233,7 @@ protected function _getLoadAttributesSelect($table, $attributeIds = []) if ($entity->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE && $entity->getTypeId()) { $select->where( - 'entity_type_id =?', + 't_d.entity_type_id =?', $entity->getTypeId() ); } @@ -1668,4 +1669,16 @@ public function removeAllFieldsFromSelect() { return $this->removeAttributeToSelect(); } + + /** + * Invalidates "Total Records Count". + * Invalidates saved "Total Records Count" attribute with last counting, + * so a next calling of method getSize() will query new total records count. + * + * @return void + */ + private function invalidateSize() + { + $this->_totalRecords = null; + } } diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index 6896bce735dde..caa9146c3d8fe 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -118,7 +118,7 @@ public function loadByCode(AbstractModel $object, $entityTypeId, $code) */ private function _getMaxSortOrder(AbstractModel $object) { - if (intval($object->getAttributeGroupId()) > 0) { + if ((int)$object->getAttributeGroupId() > 0) { $connection = $this->getConnection(); $bind = [ ':attribute_set_id' => $object->getAttributeSetId(), diff --git a/app/code/Magento/Eav/Test/Mftf/composer.json b/app/code/Magento/Eav/Test/Mftf/composer.json deleted file mode 100644 index 3c6b0cee491d3..0000000000000 --- a/app/code/Magento/Eav/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-eav", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php index 36eb78fd435e7..e16d366b099da 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/OptionManagementTest.php @@ -58,13 +58,13 @@ public function testAdd() $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeOptionLabelInterface::class); $option = ['value' => [ - 'new_option' => [ + 'id_new_option' => [ 0 => 'optionLabel', 42 => 'labelLabel', ], ], 'order' => [ - 'new_option' => 'optionSortOrder', + 'id_new_option' => 'optionSortOrder', ], ]; @@ -77,7 +77,7 @@ public function testAdd() $labelMock->expects($this->once())->method('getStoreId')->willReturn(42); $labelMock->expects($this->once())->method('getLabel')->willReturn('labelLabel'); $optionMock->expects($this->once())->method('getIsDefault')->willReturn(true); - $attributeMock->expects($this->once())->method('setDefault')->with(['new_option']); + $attributeMock->expects($this->once())->method('setDefault')->with(['id_new_option']); $attributeMock->expects($this->once())->method('setOption')->with($option); $this->resourceModelMock->expects($this->once())->method('save')->with($attributeMock); $this->assertTrue($this->model->add($entityType, $attributeCode, $optionMock)); @@ -166,13 +166,13 @@ public function testAddWithCannotSaveException() $labelMock = $this->createMock(\Magento\Eav\Api\Data\AttributeOptionLabelInterface::class); $option = ['value' => [ - 'new_option' => [ + 'id_new_option' => [ 0 => 'optionLabel', 42 => 'labelLabel', ], ], 'order' => [ - 'new_option' => 'optionSortOrder', + 'id_new_option' => 'optionSortOrder', ], ]; @@ -185,7 +185,7 @@ public function testAddWithCannotSaveException() $labelMock->expects($this->once())->method('getStoreId')->willReturn(42); $labelMock->expects($this->once())->method('getLabel')->willReturn('labelLabel'); $optionMock->expects($this->once())->method('getIsDefault')->willReturn(true); - $attributeMock->expects($this->once())->method('setDefault')->with(['new_option']); + $attributeMock->expects($this->once())->method('setDefault')->with(['id_new_option']); $attributeMock->expects($this->once())->method('setOption')->with($option); $this->resourceModelMock->expects($this->once())->method('save')->with($attributeMock) ->willThrowException(new \Exception()); diff --git a/app/code/Magento/Eav/composer.json b/app/code/Magento/Eav/composer.json index 516bd7c9493b0..2f0a7fa3c79a9 100644 --- a/app/code/Magento/Eav/composer.json +++ b/app/code/Magento/Eav/composer.json @@ -11,7 +11,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "101.0.4", + "version": "101.0.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Email/Model/AbstractTemplate.php b/app/code/Magento/Email/Model/AbstractTemplate.php index 81e993fad76df..a6ecdaf24ebbb 100644 --- a/app/code/Magento/Email/Model/AbstractTemplate.php +++ b/app/code/Magento/Email/Model/AbstractTemplate.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Email\Model; use Magento\Framework\App\Filesystem\DirectoryList; @@ -289,7 +290,7 @@ public function loadDefault($templateId) /** * trim copyright message */ - if (preg_match('/^<!--[\w\W]+?-->/m', $templateText, $matches) && strpos($matches[0], 'Copyright') > 0) { + if (preg_match('/^<!--[\w\W]+?-->/m', $templateText, $matches) && strpos($matches[0], 'Copyright') !== false) { $templateText = str_replace($matches[0], '', $templateText); } @@ -530,13 +531,13 @@ protected function cancelDesignConfig() * * @param string $templateId * @return $this - * @throws \Magento\Framework\Exception\MailException */ public function setForcedArea($templateId) { - if (!isset($this->area)) { + if ($this->area === null) { $this->area = $this->emailConfig->getTemplateArea($templateId); } + return $this; } @@ -604,7 +605,9 @@ public function getDesignConfig() public function setDesignConfig(array $config) { if (!isset($config['area']) || !isset($config['store'])) { - throw new LocalizedException(__('Design config must have area and store.')); + throw new LocalizedException( + __('The design config needs an area and a store. Verify that both are set and try again.') + ); } $this->getDesignConfig()->setData($config); return $this; diff --git a/app/code/Magento/Email/Model/Transport.php b/app/code/Magento/Email/Model/Transport.php index 7e966a9a5a28d..dc7824a228e34 100644 --- a/app/code/Magento/Email/Model/Transport.php +++ b/app/code/Magento/Email/Model/Transport.php @@ -7,13 +7,16 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Exception\MailException; -use Magento\Framework\Mail\MessageInterface; +use Magento\Framework\Mail\Message; use Magento\Framework\Mail\TransportInterface; +use Magento\Framework\Phrase; use Magento\Store\Model\ScopeInterface; +use Zend\Mail\Message as ZendMessage; +use Zend\Mail\Transport\Sendmail; /** * Class that responsible for filling some message data before transporting it. - * @see Zend_Mail_Transport_Sendmail is used for transport + * @see \Zend\Mail\Transport\Sendmail is used for transport */ class Transport implements TransportInterface { @@ -29,79 +32,81 @@ class Transport implements TransportInterface const XML_PATH_SENDING_RETURN_PATH_EMAIL = 'system/smtp/return_path_email'; /** - * Object for sending eMails + * Whether return path should be set or no. * - * @var \Zend_Mail_Transport_Sendmail + * Possible values are: + * 0 - no + * 1 - yes (set value as FROM address) + * 2 - use custom value + * + * @var int */ - private $transport; + private $isSetReturnPath; /** - * Email message object that should be instance of \Zend_Mail - * - * @var MessageInterface + * @var string|null + */ + private $returnPathValue; + + /** + * @var Sendmail + */ + private $zendTransport; + + /** + * @var Message */ private $message; /** - * Core store config - * - * @var ScopeConfigInterface + * @var ZendMessage */ - private $scopeConfig; + private $zendMessage; /** - * @param \Zend_Mail_Transport_Sendmail $transport - * @param MessageInterface $message Email message object + * @param Message $message Email message object * @param ScopeConfigInterface $scopeConfig Core store config - * @param string|array|\Zend_Config|null $parameters Config options for sendmail parameters - * - * @throws \InvalidArgumentException when $message is not an instance of \Zend_Mail + * @param ZendMessage $zendMessage + * @param null|string|array|\Traversable $parameters Config options for sendmail parameters */ public function __construct( - \Zend_Mail_Transport_Sendmail $transport, - MessageInterface $message, - ScopeConfigInterface $scopeConfig + Message $message, + ScopeConfigInterface $scopeConfig, + ZendMessage $zendMessage, + $parameters = null ) { - if (!$message instanceof \Zend_Mail) { - throw new \InvalidArgumentException('The message should be an instance of \Zend_Mail'); - } - $this->transport = $transport; + $this->isSetReturnPath = (int) $scopeConfig->getValue( + self::XML_PATH_SENDING_SET_RETURN_PATH, + ScopeInterface::SCOPE_STORE + ); + $this->returnPathValue = $scopeConfig->getValue( + self::XML_PATH_SENDING_RETURN_PATH_EMAIL, + ScopeInterface::SCOPE_STORE + ); + + $this->zendTransport = new Sendmail($parameters); $this->message = $message; - $this->scopeConfig = $scopeConfig; + $this->zendMessage = $zendMessage; } /** - * Sets Return-Path to email if necessary, and sends email if it is allowed by System Configurations - * - * @return void - * @throws MailException + * @inheritdoc */ public function sendMessage() { try { - /* configuration of whether return path should be set or no. Possible values are: - * 0 - no - * 1 - yes (set value as FROM address) - * 2 - use custom value - * @see Magento\Config\Model\Config\Source\Yesnocustom - */ - $isSetReturnPath = $this->scopeConfig->getValue( - self::XML_PATH_SENDING_SET_RETURN_PATH, - ScopeInterface::SCOPE_STORE - ); - $returnPathValue = $this->scopeConfig->getValue( - self::XML_PATH_SENDING_RETURN_PATH_EMAIL, - ScopeInterface::SCOPE_STORE - ); - - if ($isSetReturnPath == '1') { - $this->message->setReturnPath($this->message->getFrom()); - } elseif ($isSetReturnPath == '2' && $returnPathValue !== null) { - $this->message->setReturnPath($returnPathValue); + $message = $this->zendMessage->fromString($this->message->getRawMessage()); + if (2 === $this->isSetReturnPath && $this->returnPathValue) { + $message->setSender($this->returnPathValue); + } elseif (1 === $this->isSetReturnPath && $message->getFrom()->count()) { + $fromAddressList = $message->getFrom(); + $fromAddressList->rewind(); + $message->setSender($fromAddressList->current()->getEmail()); } - $this->transport->send($this->message); + + $this->zendTransport->send($message); } catch (\Exception $e) { - throw new MailException(__($e->getMessage()), $e); + throw new MailException(new Phrase($e->getMessage()), $e); } } diff --git a/app/code/Magento/Email/Test/Mftf/ActionGroup/AdminEmailTemplateActionGroup.xml b/app/code/Magento/Email/Test/Mftf/ActionGroup/AdminEmailTemplateActionGroup.xml new file mode 100644 index 0000000000000..4019e0538dd06 --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/ActionGroup/AdminEmailTemplateActionGroup.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <!--Create New Email Template --> + <actionGroup name="AdminCreateNewEmailTemplate"> + <arguments> + <argument name="emailTemplate" defaultValue="EmailTemplate"/> + </arguments> + <!--Click "Add New Template" button--> + <click selector="{{AdminMainActionsSection.add}}" stepKey="clickAddNewTemplateButton"/> + <waitForPageLoad stepKey="waitForNewEmailTemplatePageLoaded"/> + <!--Select value for "Template" drop-down menu in "Load default template" tab--> + <selectOption selector="{{AdminEmailTemplateEditSection.templateDropDown}}" userInput="Registry Update" stepKey="selectValueFromTemplateDropDown"/> + <!--Fill in required fields in "Template Information" tab and click "Save Template" button--> + <click selector="{{AdminEmailTemplateEditSection.loadTemplateButton}}" stepKey="clickLoadTemplateButton"/> + <waitForElementVisible selector="{{AdminEmailTemplateEditSection.templateNameField}}" stepKey="waitForTemplateNameFieldVisible"/> + <fillField selector="{{AdminEmailTemplateEditSection.templateNameField}}" userInput="{{emailTemplate.templateName}}" stepKey="fillTemplateNameField"/> + <fillField selector="{{AdminEmailTemplateEditSection.templateSubjectField}}" userInput="{{emailTemplate.templateSubject}}" stepKey="fillTemplateSubjectField"/> + <waitForPageLoad stepKey="waitForTemplateSaveButtonVisible"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="clickSaveTemplateButton"/> + <waitForPageLoad stepKey="waitForNewTemplateCreated"/> + <see selector="{{AdminMessagesSection.successMessage}}" userInput="You saved the email template." stepKey="seeSuccessMessageAfterSave"/> + </actionGroup> + + <!--Delete Email Template--> + <actionGroup name="AdminDeleteEmailTemplate"> + <arguments> + <argument name="emailTemplate" defaultValue="EmailTemplate"/> + </arguments> + <seeInCurrentUrl url="email_template/edit/id" stepKey="seeCreatedTemplateUrl"/> + <click selector="{{AdminMainActionsSection.delete}}" stepKey="clickDeleteTemplateButton"/> + <acceptPopup stepKey="acceptDeletingTemplatePopUp"/> + <see userInput="You deleted the email template." stepKey="seeSuccessfulMessage"/> + <click selector="{{AdminDataGridHeaderSection.clearFilters}}" stepKey="clickResetFilterButton"/> + <waitForElementNotVisible selector="{{AdminEmailTemplateGridSection.templateRowWithName(emailTemplate.templateName)}}" stepKey="waitForSearchFieldCleared"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Email/Test/Mftf/Data/EmailTemplateData.xml b/app/code/Magento/Email/Test/Mftf/Data/EmailTemplateData.xml new file mode 100644 index 0000000000000..43b32b316daad --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Data/EmailTemplateData.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="EmailTemplate" type="template"> + <data key="templateName" unique="suffix">Template</data> + <data key="templateSubject" unique="suffix">Template Subject</data> + </entity> +</entities> diff --git a/app/code/Magento/Email/Test/Mftf/Page/AdminEmailTemplateCreatePage.xml b/app/code/Magento/Email/Test/Mftf/Page/AdminEmailTemplateCreatePage.xml new file mode 100644 index 0000000000000..a279204d4e0a0 --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Page/AdminEmailTemplateCreatePage.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="AdminEmailTemplateCreatePage" url="/admin/email_template/new/" area="admin" module="Magento_Email"> + <section name="AdminEmailTemplateEditSection"/> + <section name="AdminMainActionsSection"/> + </page> +</pages> diff --git a/app/code/Magento/Email/Test/Mftf/Page/AdminEmailTemplateGridPage.xml b/app/code/Magento/Email/Test/Mftf/Page/AdminEmailTemplateGridPage.xml new file mode 100644 index 0000000000000..30dc5235a3c61 --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Page/AdminEmailTemplateGridPage.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="AdminEmailTemplateGridPage" url="/admin/email_template/" area="admin" module="Magento_Email"> + <section name="AdminEmailTemplateGridSection"/> + <section name="AdminMainActionsSection"/> + <section name="AdminMessagesSection"/> + <section name="AdminDataGridHeaderSection"/> + <section name="AdminDataGridTableSection"/> + </page> +</pages> diff --git a/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateEditSection.xml b/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateEditSection.xml new file mode 100644 index 0000000000000..9da02b64cb2e7 --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateEditSection.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEmailTemplateEditSection"> + <element name="templateDropDown" type="select" selector="#template_select"/> + <element name="loadTemplateButton" type="button" selector="#load"/> + <element name="templateNameField" type="input" selector="#template_code"/> + <element name="templateSubjectField" type="input" selector="#template_subject"/> + <element name="previewTemplateButton" type="button" selector="#preview"/> + </section> +</sections> diff --git a/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateGridSection.xml b/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateGridSection.xml new file mode 100644 index 0000000000000..b883946eb98d3 --- /dev/null +++ b/app/code/Magento/Email/Test/Mftf/Section/AdminEmailTemplateGridSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminEmailTemplateGridSection"> + <element name="searchTemplateField" type="input" selector="#systemEmailTemplateGrid_filter_code"/> + <element name="templateRowWithName" type="input" selector="//*[@id='systemEmailTemplateGrid_filter_code' and @value='{{arg2}}']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Email/Test/Mftf/composer.json b/app/code/Magento/Email/Test/Mftf/composer.json deleted file mode 100644 index f36d8b204f6d3..0000000000000 --- a/app/code/Magento/Email/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-email", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-variable": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Email/Test/Unit/Model/Plugin/WindowsSmtpConfigTest.php b/app/code/Magento/Email/Test/Unit/Model/Plugin/WindowsSmtpConfigTest.php new file mode 100644 index 0000000000000..b06dc3cb48cbc --- /dev/null +++ b/app/code/Magento/Email/Test/Unit/Model/Plugin/WindowsSmtpConfigTest.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Email\Test\Unit\Model\Plugin; + +use Magento\Email\Model\Plugin\WindowsSmtpConfig; +use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Framework\Mail\TransportInterface; +use Magento\Framework\OsInfo; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +/** + * WindowsSmtpConfigTest + */ +class WindowsSmtpConfigTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var WindowsSmtpConfig + */ + private $windowsSmtpConfig; + + /** + * @var OsInfo|\PHPUnit_Framework_MockObject_MockObject + */ + private $osInfoMock; + + /** + * @var ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var TransportInterface + */ + private $transportMock; + + /** + * setUp + * + * @return void + */ + public function setUp() + { + $objectManager = new ObjectManager($this); + + $this->osInfoMock = $this->createMock(OsInfo::class); + $this->configMock = $this->createMock(ReinitableConfigInterface::class); + $this->transportMock = $this->createMock(TransportInterface::class); + + $this->windowsSmtpConfig = $objectManager->getObject( + WindowsSmtpConfig::class, + [ + 'config' => $this->configMock, + 'osInfo' => $this->osInfoMock + ] + ); + } + + /** + * Test if SMTP settings if windows server + * + * @return void + */ + public function testBeforeSendMessageOsWindows() + { + $this->osInfoMock->expects($this->once()) + ->method('isWindows') + ->willReturn(true); + + $this->configMock->expects($this->exactly(2)) + ->method('getValue') + ->willReturnMap([ + [WindowsSmtpConfig::XML_SMTP_HOST, '127.0.0.1'], + [WindowsSmtpConfig::XML_SMTP_PORT, '80'] + ]); + + $this->windowsSmtpConfig->beforeSendMessage($this->transportMock); + } + + /** + * Test if SMTP settings if not windows server + * + * @return void + */ + public function testBeforeSendMessageOsIsWindows() + { + $this->osInfoMock->expects($this->once()) + ->method('isWindows') + ->willReturn(false); + + $this->configMock->expects($this->never()) + ->method('getValue'); + + $this->windowsSmtpConfig->beforeSendMessage($this->transportMock); + } +} diff --git a/app/code/Magento/Email/Test/Unit/Model/Template/SenderResolverTest.php b/app/code/Magento/Email/Test/Unit/Model/Template/SenderResolverTest.php new file mode 100644 index 0000000000000..8d2931f3e38f5 --- /dev/null +++ b/app/code/Magento/Email/Test/Unit/Model/Template/SenderResolverTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Email\Test\Unit\Model\Template; + +use Magento\Email\Model\Template\SenderResolver; +use Magento\Framework\Exception\MailException; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\App\Config\ScopeConfigInterface; + +/** + * SenderResolverTest + */ +class SenderResolverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var SenderResolver + */ + private $senderResolver; + + /** + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $scopeConfig; + + /** + * @return void + */ + public function setUp() + { + $objectManager = new ObjectManager($this); + + $this->scopeConfig = $this->createMock(ScopeConfigInterface::class); + + $this->senderResolver = $objectManager->getObject( + SenderResolver::class, + [ + 'scopeConfig' => $this->scopeConfig + ] + ); + } + + /** + * Test returned information for given sender's name and email + * + * @return void + */ + public function testResolve() + { + $sender = 'general'; + $scopeId = null; + + $this->scopeConfig->expects($this->exactly(2)) + ->method('getValue') + ->willReturnMap([ + [ + 'trans_email/ident_' . $sender . '/name', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $scopeId, + 'Test Name' + ], + [ + 'trans_email/ident_' . $sender . '/email', + \Magento\Store\Model\ScopeInterface::SCOPE_STORE, + $scopeId, + 'test@email.com' + ] + ]); + + $result = $this->senderResolver->resolve($sender); + + $this->assertTrue(isset($result['name'])); + $this->assertEquals('Test Name', $result['name']); + + $this->assertTrue(isset($result['email'])); + $this->assertEquals('test@email.com', $result['email']); + } + + /** + * Test if exception is thrown in case there is no name or email in result + * + * @dataProvider dataProvidedSenderArray + * @param array $sender + * + * @return void + */ + public function testResolveThrowException(array $sender) + { + $this->expectExceptionMessage('Invalid sender data'); + $this->expectException(MailException::class); + $this->senderResolver->resolve($sender); + } + + /** + * @return array + */ + public function dataProvidedSenderArray() + { + return [ + [ + ['name' => 'Name'] + ], + [ + ['email' => 'test@email.com'] + ] + ]; + } +} diff --git a/app/code/Magento/Email/Test/Unit/Model/TransportTest.php b/app/code/Magento/Email/Test/Unit/Model/TransportTest.php deleted file mode 100644 index 3589e43996936..0000000000000 --- a/app/code/Magento/Email/Test/Unit/Model/TransportTest.php +++ /dev/null @@ -1,192 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Email\Test\Unit\Model; - -use Magento\Email\Model\Transport; -use Magento\Framework\App\Config\ScopeConfigInterface; -use Magento\Framework\Mail\Message; -use Magento\Store\Model\ScopeInterface; - -/** - * Covers \Magento\Email\Model\Transport - */ -class TransportTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Zend_Mail_Transport_Sendmail|\PHPUnit_Framework_MockObject_MockObject - */ - private $transportMock; - - /** - * @var Message|\PHPUnit_Framework_MockObject_MockObject - */ - private $messageMock; - - /** - * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $scopeConfigMock; - - /** - * @var Transport - */ - private $model; - - protected function setUp() - { - $this->transportMock = $this->createMock(\Zend_Mail_Transport_Sendmail::class); - - $this->messageMock = $this->createMock(Message::class); - - $this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class); - - $this->model = new Transport($this->transportMock, $this->messageMock, $this->scopeConfigMock); - } - - /** - * Tests that if any exception was caught, \Magento\Framework\Exception\MailException will thrown - * - * @expectedException \Magento\Framework\Exception\MailException - */ - public function testSendMessageException() - { - $this->scopeConfigMock->expects($this->once()) - ->method('getValue') - ->willThrowException(new \Exception('some exception')); - $this->model->sendMessage(); - } - - /** - * Tests that if sending Return-Path was disabled or email was not provided, - this header won't be set - * - * @param string|int|null $returnPathSet - * @param string|null $returnPathEmail - * - * @dataProvider sendMessageWithoutReturnPathDataProvider - */ - public function testSendMessageWithoutReturnPath($returnPathSet, $returnPathEmail = null) - { - $this->prepareSendingMessage($returnPathSet, $returnPathEmail); - - $this->messageMock->expects($this->never()) - ->method('setReturnPath'); - $this->transportMock->expects($this->once()) - ->method('send'); - $this->model->sendMessage(); - } - - /** - * Tests that if sending Return-Path was disabled, this header won't be set - * - * @param string|int|null $returnPathSet - * @param string|null $emailFrom - * - * @dataProvider sendMessageWithDefaultReturnPathDataProvider - */ - public function testSendMessageWithDefaultReturnPath($returnPathSet, $emailFrom) - { - $this->prepareSendingMessage($returnPathSet, null); - - $this->messageMock->expects($this->once()) - ->method('setReturnPath') - ->with($emailFrom); - $this->messageMock->expects($this->once()) - ->method('getFrom') - ->willReturn($emailFrom); - $this->transportMock->expects($this->once()) - ->method('send'); - $this->model->sendMessage(); - } - - /** - * Tests that if sending Return-Path was disabled, this header won't be set - * - * @param string|int|null $returnPathSet - * @param string|null $emailFrom - * - * @dataProvider sendMessageWithCustomReturnPathDataProvider - */ - public function testSendMessageWithCustomReturnPath($returnPathSet, $emailFrom) - { - $this->prepareSendingMessage($returnPathSet, $emailFrom); - - $this->messageMock->expects($this->once()) - ->method('setReturnPath') - ->with($emailFrom); - $this->messageMock->expects($this->never()) - ->method('getFrom') - ->willReturn($emailFrom); - $this->transportMock->expects($this->once()) - ->method('send'); - $this->model->sendMessage(); - } - - /** - * Tests retrieving message object - */ - public function testGetMessage() - { - $this->assertEquals($this->messageMock, $this->model->getMessage()); - } - - /** - * Executes all main sets for sending message - * - * @param string|int|null $returnPathSet - * @param string|null $returnPathEmail - */ - private function prepareSendingMessage($returnPathSet, $returnPathEmail) - { - $map = [ - [Transport::XML_PATH_SENDING_SET_RETURN_PATH, ScopeInterface::SCOPE_STORE, null, $returnPathSet], - [Transport::XML_PATH_SENDING_RETURN_PATH_EMAIL, ScopeInterface::SCOPE_STORE, null, $returnPathEmail] - ]; - $this->scopeConfigMock->expects($this->exactly(2)) - ->method('getValue') - ->willReturnMap($map); - } - - /** - * Data provider for testSendMessageWithoutReturnPath - * @return array - */ - public function sendMessageWithoutReturnPathDataProvider() - { - return [ - [0], - ['0'], - [3], - ['2', null], - [2, null], - ]; - } - - /** - * Data provider for testSendMessageWithDefaultReturnPath - * @return array - */ - public function sendMessageWithDefaultReturnPathDataProvider() - { - return [ - [1, 'test@exemple.com'], - ['1', 'test@exemple.com'], - ['1', ''] - ]; - } - - /** - * Data provider for testSendMessageWithCustomReturnPath - * @return array - */ - public function sendMessageWithCustomReturnPathDataProvider() - { - return [ - [2, 'test@exemple.com'], - ['2', 'test@exemple.com'], - ['2', ''] - ]; - } -} diff --git a/app/code/Magento/Email/composer.json b/app/code/Magento/Email/composer.json index 6b96387a80728..bffc825a4c872 100644 --- a/app/code/Magento/Email/composer.json +++ b/app/code/Magento/Email/composer.json @@ -15,7 +15,7 @@ "magento/module-theme": "100.2.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Email/view/adminhtml/templates/template/edit.phtml b/app/code/Magento/Email/view/adminhtml/templates/template/edit.phtml index 3c89e8c2a9308..c1beb3ecb5a77 100644 --- a/app/code/Magento/Email/view/adminhtml/templates/template/edit.phtml +++ b/app/code/Magento/Email/view/adminhtml/templates/template/edit.phtml @@ -150,10 +150,10 @@ require([ } else { $('preview_type').value = <?= (int) $block->getTemplateType() ?>; } - if (typeof tinyMCE == 'undefined' || !tinyMCE.getInstanceById('template_text')) { + if (typeof tinyMCE == 'undefined' || !tinyMCE.get('template_text')) { $('preview_text').value = $('template_text').value; } else { - $('preview_text').value = tinyMCE.getInstanceById('template_text').getHTML(); + $('preview_text').value = tinyMCE.get('template_text').getHTML(); } if ($('template_styles') != undefined) { diff --git a/app/code/Magento/EncryptionKey/Test/Mftf/composer.json b/app/code/Magento/EncryptionKey/Test/Mftf/composer.json deleted file mode 100644 index 6cd7973983d82..0000000000000 --- a/app/code/Magento/EncryptionKey/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-encryption-key", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/EncryptionKey/composer.json b/app/code/Magento/EncryptionKey/composer.json index 931fc8888af43..57298d2a9736b 100644 --- a/app/code/Magento/EncryptionKey/composer.json +++ b/app/code/Magento/EncryptionKey/composer.json @@ -8,7 +8,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "proprietary" ], diff --git a/app/code/Magento/Fedex/Test/Mftf/composer.json b/app/code/Magento/Fedex/Test/Mftf/composer.json deleted file mode 100644 index e6e85480f0951..0000000000000 --- a/app/code/Magento/Fedex/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-fedex", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Fedex/composer.json b/app/code/Magento/Fedex/composer.json index a28a124552e10..55ff9fb83eb68 100644 --- a/app/code/Magento/Fedex/composer.json +++ b/app/code/Magento/Fedex/composer.json @@ -15,7 +15,7 @@ "lib-libxml": "*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/GiftMessage/Test/Mftf/composer.json b/app/code/Magento/GiftMessage/Test/Mftf/composer.json deleted file mode 100644 index 59a9b2e6689cb..0000000000000 --- a/app/code/Magento/GiftMessage/Test/Mftf/composer.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "magento/functional-test-module-gift-message", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-multishipping": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GiftMessage/Test/Unit/Observer/SalesEventQuoteMergeTest.php b/app/code/Magento/GiftMessage/Test/Unit/Observer/SalesEventQuoteMergeTest.php new file mode 100644 index 0000000000000..e7d9277593350 --- /dev/null +++ b/app/code/Magento/GiftMessage/Test/Unit/Observer/SalesEventQuoteMergeTest.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\GiftMessage\Test\Unit\Observer; + +use Magento\GiftMessage\Observer\SalesEventQuoteMerge; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\Event\Observer; +use Magento\Quote\Model\Quote; + +/** + * SalesEventQuoteMergeTest + */ +class SalesEventQuoteMergeTest extends \PHPUnit\Framework\TestCase +{ + + /** + * @var SalesEventQuoteMerge + */ + private $salesEventQuoteMerge; + + /** + * @return void + */ + public function setUp() + { + $objectManger = new ObjectManager($this); + $this->salesEventQuoteMerge = $objectManger->getObject(SalesEventQuoteMerge::class); + } + + /** + * @dataProvider dataProviderGiftMessageId + * + * @param null|int $giftMessageId + * + * @return void + */ + public function testExecute($giftMessageId) + { + $sourceQuoteMock = $this->createPartialMock(Quote::class, ['getGiftMessageId']); + $sourceQuoteMock->expects($this->once()) + ->method('getGiftMessageId') + ->willReturn($giftMessageId); + + $targetQuoteMock = $this->createPartialMock(Quote::class, ['setGiftMessageId']); + + if ($giftMessageId) { + $targetQuoteMock->expects($this->once()) + ->method('setGiftMessageId'); + } else { + $targetQuoteMock->expects($this->never()) + ->method('setGiftMessageId'); + } + + $observer = $this->createMock(Observer::class); + $observer->expects($this->exactly(2)) + ->method('getData') + ->willReturnMap([ + ['quote', null, $targetQuoteMock], + ['source', null, $sourceQuoteMock] + ]); + + $this->salesEventQuoteMerge->execute($observer); + } + + /** + * @return array + */ + public function dataProviderGiftMessageId(): array + { + return [ + [null], + [1] + ]; + } +} diff --git a/app/code/Magento/GiftMessage/composer.json b/app/code/Magento/GiftMessage/composer.json index 49067e3f64a1b..6a098af78e28b 100644 --- a/app/code/Magento/GiftMessage/composer.json +++ b/app/code/Magento/GiftMessage/composer.json @@ -17,7 +17,7 @@ "magento/module-multishipping": "100.2.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/GoogleAdwords/Test/Mftf/composer.json b/app/code/Magento/GoogleAdwords/Test/Mftf/composer.json deleted file mode 100644 index 5b1bbbf7599da..0000000000000 --- a/app/code/Magento/GoogleAdwords/Test/Mftf/composer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "magento/functional-test-module-google-adwords", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GoogleAdwords/composer.json b/app/code/Magento/GoogleAdwords/composer.json index 6a72acaae8747..da44eb6c785d2 100644 --- a/app/code/Magento/GoogleAdwords/composer.json +++ b/app/code/Magento/GoogleAdwords/composer.json @@ -8,7 +8,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/GoogleAnalytics/Test/Mftf/composer.json b/app/code/Magento/GoogleAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 85b45a07487b8..0000000000000 --- a/app/code/Magento/GoogleAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-google-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-cookie": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GoogleAnalytics/composer.json b/app/code/Magento/GoogleAnalytics/composer.json index 221eea4b0e09d..a5ab4caff2927 100644 --- a/app/code/Magento/GoogleAnalytics/composer.json +++ b/app/code/Magento/GoogleAnalytics/composer.json @@ -12,7 +12,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/GoogleAnalytics/view/frontend/layout/default.xml b/app/code/Magento/GoogleAnalytics/view/frontend/layout/default.xml index 892a22129adfd..c3f34c5abc1df 100644 --- a/app/code/Magento/GoogleAnalytics/view/frontend/layout/default.xml +++ b/app/code/Magento/GoogleAnalytics/view/frontend/layout/default.xml @@ -7,8 +7,8 @@ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> - <referenceContainer name="head.additional"> + <referenceBlock name="head.additional"> <block class="Magento\GoogleAnalytics\Block\Ga" name="google_analytics" as="google_analytics" template="Magento_GoogleAnalytics::ga.phtml"/> - </referenceContainer> + </referenceBlock> </body> </page> diff --git a/app/code/Magento/GoogleOptimizer/Test/Mftf/composer.json b/app/code/Magento/GoogleOptimizer/Test/Mftf/composer.json deleted file mode 100644 index 77f22bb9c9907..0000000000000 --- a/app/code/Magento/GoogleOptimizer/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-google-optimizer", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-google-analytics": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GoogleOptimizer/composer.json b/app/code/Magento/GoogleOptimizer/composer.json index 4ee4ab5f4f7e5..a84645b897298 100644 --- a/app/code/Magento/GoogleOptimizer/composer.json +++ b/app/code/Magento/GoogleOptimizer/composer.json @@ -12,7 +12,7 @@ "magento/module-ui": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php b/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php index 83ffc3dfb253e..b76f026872b81 100644 --- a/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php +++ b/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php @@ -89,7 +89,7 @@ public function saveData() foreach ($associatedSkusAndQtyPairs as $associatedSkuAndQty) { ++$position; $associatedSkuAndQty = explode(self::SKU_QTY_DELIMITER, $associatedSkuAndQty); - $associatedSku = isset($associatedSkuAndQty[0]) ? trim($associatedSkuAndQty[0]) : null; + $associatedSku = isset($associatedSkuAndQty[0]) ? strtolower(trim($associatedSkuAndQty[0])) : null; if (isset($newSku[$associatedSku])) { $linkedProductId = $newSku[$associatedSku][$this->getProductEntityIdentifierField()]; } elseif (isset($oldSku[$associatedSku])) { diff --git a/app/code/Magento/GroupedImportExport/Test/Mftf/composer.json b/app/code/Magento/GroupedImportExport/Test/Mftf/composer.json deleted file mode 100644 index 44a53742e28d6..0000000000000 --- a/app/code/Magento/GroupedImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-grouped-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-import-export": "100.0.0-dev", - "magento/functional-test-module-catalog-import-export": "100.0.0-dev", - "magento/functional-test-module-grouped-product": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GroupedImportExport/composer.json b/app/code/Magento/GroupedImportExport/composer.json index c8c9c0148b79f..09d5f8894b354 100644 --- a/app/code/Magento/GroupedImportExport/composer.json +++ b/app/code/Magento/GroupedImportExport/composer.json @@ -11,7 +11,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php index e1c97c8912cf8..b6a3aede21ad2 100644 --- a/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php +++ b/app/code/Magento/GroupedProduct/Model/Product/Type/Grouped.php @@ -341,7 +341,7 @@ protected function getProductInfo(\Magento\Framework\DataObject $buyRequest, $pr if ($isStrictProcessMode && !$subProduct->getQty()) { return __('Please specify the quantity of product(s).')->render(); } - $productsInfo[$subProduct->getId()] = intval($subProduct->getQty()); + $productsInfo[$subProduct->getId()] = (int)$subProduct->getQty(); } } diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml new file mode 100644 index 0000000000000..e760b877fa33d --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ApiGroupedProduct" type="product3"> + <data key="sku" unique="suffix">api-grouped-product</data> + <data key="type_id">grouped</data> + <data key="attribute_set_id">4</data> + <data key="name" unique="suffix">Api Grouped Product</data> + <data key="status">1</data> + <data key="urlKey" unique="suffix">api-grouped-product</data> + <requiredEntity type="product_extension_attribute">EavStockItem</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductDescription</requiredEntity> + <requiredEntity type="custom_attribute_array">ApiProductShortDescription</requiredEntity> + </entity> +</entities> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml new file mode 100644 index 0000000000000..362e595593783 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkData.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="ProductLinkSimple1" type="product_link"> + <var key="sku" entityKey="sku" entityType="product3"/> + <var key="linked_product_sku" entityKey="sku" entityType="product"/> + <data key="link_type">associated</data> + <data key="linked_product_type">simple</data> + <data key="position">1</data> + <requiredEntity type="product_link_extension_attribute">Qty1000</requiredEntity> + </entity> + <entity name="ProductLinkSimple2" type="product_link" extends="ProductLinkSimple1"> + <data key="position">2</data> + </entity> +</entities> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml new file mode 100644 index 0000000000000..b580c876a6f30 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinkExtensionAttributeData.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="Qty1000" type="product_link_extension_attribute"> + <data key="qty">1000</data> + </entity> +</entities> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml new file mode 100644 index 0000000000000..68c95e856e2f8 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/ProductLinksData.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="OneSimpleProductLink" type="product_links"> + <requiredEntity type="product_link">ProductLinkSimple1</requiredEntity> + </entity> + <entity name="OneMoreSimpleProductLink" type="product_links"> + <requiredEntity type="product_link">ProductLinkSimple2</requiredEntity> + </entity> + <entity name="TwoSimpleProductLinks" type="product_links"> + <array key="items"> + <item>ProductLinkSimple1</item> + <item>ProductLinkSimple2</item> + </array> + </entity> +</entities> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Metadata/product-meta.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Metadata/product-meta.xml new file mode 100644 index 0000000000000..87fa0a6379dd3 --- /dev/null +++ b/app/code/Magento/GroupedProduct/Test/Mftf/Metadata/product-meta.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <!-- Data type product3 is to work around MQE-1035 --> + <operation name="CreateProduct3" dataType="product3" type="create" auth="adminOauth" url="/V1/products" method="POST"> + <contentType>application/json</contentType> + <object dataType="product3" key="product"> + <field key="sku">string</field> + <field key="name">string</field> + <field key="attribute_set_id">integer</field> + <field key="price">number</field> + <field key="status">integer</field> + <field key="visibility">integer</field> + <field key="type_id">string</field> + <field key="created_at">string</field> + <field key="updated_at">string</field> + <field key="weight">integer</field> + <field key="extension_attributes">product_extension_attribute</field> + <array key="product_links"> + <value>product_link</value> + </array> + <array key="custom_attributes"> + <value>custom_attribute_array</value> + </array> + <array key="options"> + <value>product_option</value> + </array> + </object> + </operation> + <!-- Data type product3 is to work around MQE-1035 --> + <operation name="DeleteProduct3" dataType="product3" type="delete" auth="adminOauth" url="/V1/products/{sku}" method="DELETE"> + <contentType>application/json</contentType> + </operation> + <operation name="GetProduct3" dataType="product3" type="get" auth="adminOauth" url="/V1/products/{sku}" method="GET"> + <contentType>application/json</contentType> + </operation> +</operations> diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/composer.json b/app/code/Magento/GroupedProduct/Test/Mftf/composer.json deleted file mode 100644 index 12ee01afc9955..0000000000000 --- a/app/code/Magento/GroupedProduct/Test/Mftf/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "magento/functional-test-module-grouped-product", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-msrp": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-grouped-product-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/GroupedProduct/composer.json b/app/code/Magento/GroupedProduct/composer.json index 828f65544eb07..c2031fc3332f0 100644 --- a/app/code/Magento/GroupedProduct/composer.json +++ b/app/code/Magento/GroupedProduct/composer.json @@ -21,7 +21,7 @@ "magento/module-grouped-product-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/GroupedProduct/view/base/templates/product/price/final_price.phtml b/app/code/Magento/GroupedProduct/view/base/templates/product/price/final_price.phtml index 34de1a18cf28a..d9238e9794d7e 100644 --- a/app/code/Magento/GroupedProduct/view/base/templates/product/price/final_price.phtml +++ b/app/code/Magento/GroupedProduct/view/base/templates/product/price/final_price.phtml @@ -26,7 +26,7 @@ if ($minProduct) { ); } ?> -<div class="price-box" itemprop="offers" itemscope itemtype="http://schema.org/Offer"> +<div class="price-box"> <?php if ($minProduct && \Magento\Framework\Pricing\Render::ZONE_ITEM_VIEW != $block->getZone()): ?> <p class="minimal-price"> <span class="price-label"><?= /* @escapeNotVerified */ __('Starting at') ?></span><?= $amountRender->toHtml() ?> diff --git a/app/code/Magento/ImportExport/Test/Mftf/composer.json b/app/code/Magento/ImportExport/Test/Mftf/composer.json deleted file mode 100644 index 1b8e2030d5fdf..0000000000000 --- a/app/code/Magento/ImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ImportExport/composer.json b/app/code/Magento/ImportExport/composer.json index 2e89ba92a1e10..affe85aed48bd 100644 --- a/app/code/Magento/ImportExport/composer.json +++ b/app/code/Magento/ImportExport/composer.json @@ -12,7 +12,7 @@ "ext-ctype": "*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Indexer/Console/Command/IndexerSetDimensionsModeCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerSetDimensionsModeCommand.php new file mode 100644 index 0000000000000..30f57d09f508b --- /dev/null +++ b/app/code/Magento/Indexer/Console/Command/IndexerSetDimensionsModeCommand.php @@ -0,0 +1,203 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Indexer\Console\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Input\InputArgument; +use Magento\Framework\App\ObjectManagerFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Console\Cli; +use Magento\Indexer\Model\ModeSwitcherInterface; + +/** + * Command to set indexer dimensions mode + */ +class IndexerSetDimensionsModeCommand extends AbstractIndexerCommand +{ + const INPUT_KEY_MODE = 'mode'; + const INPUT_KEY_INDEXER = 'indexer'; + const DIMENSION_MODE_NONE = 'none'; + const XML_PATH_DIMENSIONS_MODE_MASK = 'indexer/%s/dimensions_mode'; + + /** + * @var string + */ + private $commandName = 'indexer:set-dimensions-mode'; + + /** + * ScopeConfigInterface + * + * @var ScopeConfigInterface + */ + private $configReader; + + /** + * @var ModeSwitcherInterface[] + */ + private $dimensionProviders; + + /** + * @param ObjectManagerFactory $objectManagerFactory + * @param ScopeConfigInterface $configReader + * @param ModeSwitcherInterface[] $dimensionSwitchers + */ + public function __construct( + ObjectManagerFactory $objectManagerFactory, + ScopeConfigInterface $configReader, + array $dimensionSwitchers + ) { + $this->configReader = $configReader; + $this->dimensionProviders = $dimensionSwitchers; + parent::__construct($objectManagerFactory); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName($this->commandName) + ->setDescription('Set Indexer Dimensions Mode') + ->setDefinition($this->getInputList()); + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $errors = $this->validate($input); + if ($errors) { + throw new \InvalidArgumentException(implode(PHP_EOL, $errors)); + } + $returnValue = Cli::RETURN_SUCCESS; + /** @var \Magento\Indexer\Model\Indexer $indexer */ + $indexer = $this->getObjectManager()->get(\Magento\Indexer\Model\Indexer::class); + try { + $selectedIndexer = (string)$input->getArgument(self::INPUT_KEY_INDEXER); + if (!$selectedIndexer) { + $this->showAvailableModes($output); + } else { + $indexer->load($selectedIndexer); + $currentMode = $input->getArgument(self::INPUT_KEY_MODE); + $configPath = sprintf(self::XML_PATH_DIMENSIONS_MODE_MASK, $selectedIndexer); + $previousMode = $this->configReader->getValue($configPath) ?: self::DIMENSION_MODE_NONE; + if ($previousMode !== $currentMode) { + /** @var ModeSwitcherInterface $modeSwitcher */ + $modeSwitcher = $this->dimensionProviders[$selectedIndexer]; + // Switch dimensions mode + $modeSwitcher->switchMode($currentMode, $previousMode); + $output->writeln( + 'Dimensions mode for indexer "' . $indexer->getTitle() . '" was changed from \'' + . $previousMode . '\' to \'' . $currentMode . '\'' + ); + } else { + $output->writeln('Dimensions mode for indexer "' . $indexer->getTitle() . '" has not been changed'); + } + } + } catch (\Exception $e) { + $output->writeln('"' . $indexer->getTitle() . '" indexer process unknown error:' . PHP_EOL); + $output->writeln($e->getMessage() . PHP_EOL); + // we must have an exit code higher than zero to indicate something was wrong + $returnValue = Cli::RETURN_FAILURE; + } + + return $returnValue; + } + + /** + * Display all available indexers and modes + * + * @param OutputInterface $output + * @return void + */ + private function showAvailableModes(OutputInterface $output) + { + $output->writeln(sprintf('%-50s', 'Indexer') . 'Available modes'); + foreach ($this->dimensionProviders as $indexer => $provider) { + $availableModes = implode(',', array_keys($provider->getDimensionModes()->getDimensions())); + $output->writeln(sprintf('%-50s', $indexer) . $availableModes); + } + } + + /** + * Get list of arguments for the command + * + * @return InputArgument[] + */ + private function getInputList(): array + { + $dimensionProvidersList = array_keys($this->dimensionProviders); + $indexerOptionDescription = 'Indexer name [' . implode('|', $dimensionProvidersList) . ']'; + $arguments[] = new InputArgument( + self::INPUT_KEY_INDEXER, + InputArgument::OPTIONAL, + $indexerOptionDescription + ); + $modeOptionDescription = 'Indexer dimension modes' . PHP_EOL; + foreach ($this->dimensionProviders as $indexer => $provider) { + $availableModes = implode(',', array_keys($provider->getDimensionModes()->getDimensions())); + $modeOptionDescription .= sprintf('%-30s ', $indexer) . $availableModes . PHP_EOL; + } + $arguments[] = new InputArgument( + self::INPUT_KEY_MODE, + InputArgument::OPTIONAL, + $modeOptionDescription + ); + + return $arguments; + } + + /** + * Check if all arguments are provided + * + * @param InputInterface $input + * @return string[] + */ + private function validate(InputInterface $input): array + { + $errors = []; + $inputIndexer = (string)$input->getArgument(self::INPUT_KEY_INDEXER); + if ($inputIndexer) { + $acceptedValues = array_keys($this->dimensionProviders); + $errors = $this->validateArgument(self::INPUT_KEY_INDEXER, $inputIndexer, $acceptedValues); + if (!$errors) { + $inputIndexerDimensionMode = (string)$input->getArgument(self::INPUT_KEY_MODE); + /** @var ModeSwitcherInterface $modeSwitcher */ + $modeSwitcher = $this->dimensionProviders[$inputIndexer]; + $acceptedValues = array_keys($modeSwitcher->getDimensionModes()->getDimensions()); + $errors = $this->validateArgument(self::INPUT_KEY_MODE, $inputIndexerDimensionMode, $acceptedValues); + } + } + + return $errors; + } + + /** + * Validate command argument and return errors in case if argument is invalid + * + * @param string $inputKey + * @param string $inputIndexer + * @param array $acceptedValues + * @return string[] + */ + private function validateArgument(string $inputKey, string $inputIndexer, array $acceptedValues): array + { + $errors = []; + $acceptedIndexerValues = ' Accepted values for "<' . $inputKey . '>" are \'' . + implode(',', $acceptedValues) . '\''; + if (!$inputIndexer) { + $errors[] = 'Missing argument "<' . $inputKey . '>".' . $acceptedIndexerValues; + } elseif (!\in_array($inputIndexer, $acceptedValues)) { + $errors[] = 'Invalid value for "<' . $inputKey . '>" argument.' . $acceptedIndexerValues; + } + + return $errors; + } +} diff --git a/app/code/Magento/Indexer/Console/Command/IndexerShowDimensionsModeCommand.php b/app/code/Magento/Indexer/Console/Command/IndexerShowDimensionsModeCommand.php new file mode 100644 index 0000000000000..f5553c3fb3546 --- /dev/null +++ b/app/code/Magento/Indexer/Console/Command/IndexerShowDimensionsModeCommand.php @@ -0,0 +1,152 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Console\Command; + +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; +use Magento\Framework\App\ObjectManagerFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Console\Cli; +use Symfony\Component\Console\Input\InputArgument; + +/** + * Command to show indexers dimension modes + */ +class IndexerShowDimensionsModeCommand extends AbstractIndexerCommand +{ + const INPUT_KEY_INDEXER = 'indexer'; + const DIMENSION_MODE_NONE = 'none'; + const XML_PATH_DIMENSIONS_MODE_MASK = 'indexer/%s/dimensions_mode'; + /** + * @var string + */ + private $commandName = 'indexer:show-dimensions-mode'; + /** + * ScopeConfigInterface + * + * @var ScopeConfigInterface + */ + private $configReader; + /** + * @var string[] + */ + private $indexers; + + /** + * @param ObjectManagerFactory $objectManagerFactory + * @param ScopeConfigInterface $configReader + * @param array $indexers + */ + public function __construct( + ObjectManagerFactory $objectManagerFactory, + ScopeConfigInterface $configReader, + array $indexers + ) { + $this->configReader = $configReader; + $this->indexers = $indexers; + parent::__construct($objectManagerFactory); + } + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName($this->commandName) + ->setDescription('Shows Indexer Dimension Mode') + ->setDefinition($this->getInputList()); + parent::configure(); + } + + /** + * {@inheritdoc} + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $errors = $this->validate($input); + if ($errors) { + throw new \InvalidArgumentException(implode(PHP_EOL, $errors)); + } + $returnValue = Cli::RETURN_SUCCESS; + /** @var \Magento\Indexer\Model\Indexer $indexer */ + $indexer = $this->getObjectManager()->get(\Magento\Indexer\Model\Indexer::class); + try { + $selectedIndexers = $input->getArgument(self::INPUT_KEY_INDEXER); + if ($selectedIndexers) { + $indexersList = (array)$selectedIndexers; + } else { + $indexersList = $this->indexers; + } + foreach ($indexersList as $indexerId) { + $indexer->load($indexerId); + $configPath = sprintf(self::XML_PATH_DIMENSIONS_MODE_MASK, $indexerId); + $mode = $this->configReader->getValue($configPath) ?: self::DIMENSION_MODE_NONE; + $output->writeln(sprintf('%-50s ', $indexer->getTitle() . ':') . $mode); + } + } catch (\Exception $e) { + $output->writeln('"' . $indexer->getTitle() . '" indexer process unknown error:' . PHP_EOL); + $output->writeln($e->getMessage() . PHP_EOL); + // we must have an exit code higher than zero to indicate something was wrong + $returnValue = Cli::RETURN_FAILURE; + } + + return $returnValue; + } + + /** + * Get list of arguments for the command + * + * @return InputArgument[] + */ + private function getInputList(): array + { + $optionDescription = 'Space-separated list of index types or omit to apply to all indexes'; + $arguments[] = new InputArgument( + self::INPUT_KEY_INDEXER, + InputArgument::OPTIONAL | InputArgument::IS_ARRAY, + $optionDescription . ' (' . implode($this->indexers) . ')' + ); + + return $arguments; + } + + /** + * Check if all arguments are provided + * + * @param InputInterface $input + * @return string[] + */ + private function validate(InputInterface $input): array + { + $inputIndexer = (array)$input->getArgument(self::INPUT_KEY_INDEXER); + $acceptedValues = array_keys($this->indexers); + $errors = $this->validateArgument(self::INPUT_KEY_INDEXER, $inputIndexer, $acceptedValues); + + return $errors; + } + + /** + * Validate command argument and return errors in case if argument is invalid + * + * @param string $inputKey + * @param array $inputIndexer + * @param array $acceptedValues + * @return array + */ + private function validateArgument(string $inputKey, array $inputIndexer, array $acceptedValues): array + { + $errors = []; + $acceptedIndexerValues = ' Accepted values for "<' . $inputKey . '>" are \'' . + implode(',', $acceptedValues) . '\''; + if (!empty($inputIndexer) && !\array_intersect($inputIndexer, $acceptedValues)) { + $errors[] = 'Invalid value for "<' . $inputKey . '>" argument.' . $acceptedIndexerValues; + } + + return $errors; + } +} diff --git a/app/code/Magento/Indexer/Model/DimensionMode.php b/app/code/Magento/Indexer/Model/DimensionMode.php new file mode 100644 index 0000000000000..74fb85e5420ff --- /dev/null +++ b/app/code/Magento/Indexer/Model/DimensionMode.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Model; + +/** + * DTO to work with dimension mode + */ +class DimensionMode +{ + /** + * @var array + */ + private $name; + + /** + * @var array + */ + private $dimensions; + + /** + * @param string $name + * @param array $dimensions + */ + public function __construct(string $name, array $dimensions) + { + $this->dimensions = (function (string ...$dimensions) { + return $dimensions; + })(...$dimensions); + $this->name = $name; + } + + /** + * Returns dimension name + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Returns dimension modes + * + * @return string[] + */ + public function getDimensions(): array + { + return $this->dimensions; + } +} diff --git a/app/code/Magento/Indexer/Model/DimensionModes.php b/app/code/Magento/Indexer/Model/DimensionModes.php new file mode 100644 index 0000000000000..bbdee0ced8662 --- /dev/null +++ b/app/code/Magento/Indexer/Model/DimensionModes.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Model; + +/** + * DTO to work with dimension modes + */ +class DimensionModes +{ + /** + * @var DimensionMode[] + */ + private $dimensions; + + /** + * @param DimensionMode[] $dimensions + */ + public function __construct(array $dimensions) + { + $this->dimensions = (function (DimensionMode ...$dimensions) { + $result = []; + foreach ($dimensions as $dimension) { + $result[$dimension->getName()] = $dimension; + }; + return $result; + })(...$dimensions); + } + + /** + * Returns dimensions and their modes + * + * @return array + */ + public function getDimensions(): array + { + return $this->dimensions; + } +} diff --git a/app/code/Magento/Indexer/Model/ModeSwitcherInterface.php b/app/code/Magento/Indexer/Model/ModeSwitcherInterface.php new file mode 100644 index 0000000000000..8984b05365fb9 --- /dev/null +++ b/app/code/Magento/Indexer/Model/ModeSwitcherInterface.php @@ -0,0 +1,32 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Model; + +/** + * Interface to switch indexer mode + */ +interface ModeSwitcherInterface +{ + /** + * Returns data object that contains dimension modes + * + * @return DimensionModes + */ + public function getDimensionModes(): DimensionModes; + + /** + * Switch dimension mode + * + * @param string $currentMode + * @param string $previousMode + * @throws \InvalidArgumentException + * @throws \Zend_Db_Exception + * @return void + */ + public function switchMode(string $currentMode, string $previousMode); +} diff --git a/app/code/Magento/Indexer/Test/Mftf/composer.json b/app/code/Magento/Indexer/Test/Mftf/composer.json deleted file mode 100644 index 115f616eeec05..0000000000000 --- a/app/code/Magento/Indexer/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-indexer", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php new file mode 100644 index 0000000000000..a5521325e589c --- /dev/null +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerSetDimensionsModeCommandTest.php @@ -0,0 +1,222 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Indexer\Test\Unit\Console\Command; + +use Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +/** + * Test for class \Magento\Indexer\Model\ModeSwitcherInterface. + */ +class IndexerSetDimensionsModeCommandTest extends AbstractIndexerCommandCommonSetup +{ + /** + * Command being tested + * + * @var IndexerSetDimensionsModeCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $command; + + /** + * ScopeConfigInterface + * + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configReaderMock; + + /** + * @var \Magento\Indexer\Model\ModeSwitcherInterface[] + */ + private $dimensionProviders; + + /** + * @var \Magento\Indexer\Model\ModeSwitcherInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $dimensionModeSwitcherMock; + + /** + * @var \Magento\Indexer\Model\Indexer|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerMock; + + /** + * @var \Magento\Indexer\Model\DimensionModes|\PHPUnit_Framework_MockObject_MockObject + */ + private $dimensionModes; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->configReaderMock = $this->createMock(ScopeConfigInterface::class); + $this->dimensionModeSwitcherMock = + $this->createMock(\Magento\Indexer\Model\ModeSwitcherInterface::class); + $this->dimensionProviders = [ + 'indexer_title' => $this->dimensionModeSwitcherMock, + ]; + $this->dimensionModes = $this->createMock(\Magento\Indexer\Model\DimensionModes::class); + $this->command = $objectManagerHelper->getObject( + IndexerSetDimensionsModeCommand::class, + [ + 'objectManagerFactory' => $this->objectManagerFactory, + 'configReader' => $this->configReaderMock, + 'dimensionSwitchers' => $this->dimensionProviders, + ] + ); + } + + /** + * Get return value map for object manager + * + * @return array + */ + protected function getObjectManagerReturnValueMap() + { + $result = parent::getObjectManagerReturnValueMap(); + $this->indexerMock = $this->createMock(\Magento\Indexer\Model\Indexer::class); + $result[] = [\Magento\Indexer\Model\Indexer::class, $this->indexerMock]; + + return $result; + } + + /** + * Tests method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute + * + * @param $indexerTitle + * @param $previousMode + * @param $command + * @param $consoleOutput + * @dataProvider dimensionModesDataProvider + * @return void + */ + public function testExecuteWithAttributes($indexerTitle, $previousMode, $command, $consoleOutput) + { + $this->configureAdminArea(); + $commandTester = new CommandTester($this->command); + $this->dimensionModes->method('getDimensions')->willReturn([ + $previousMode => 'dimension1', + $command['mode'] => 'dimension2', + ]); + $this->dimensionModeSwitcherMock->method('getDimensionModes')->willReturn($this->dimensionModes); + $this->indexerMock->method('load')->willReturnSelf(); + $this->indexerMock->method('getTitle')->willReturn($indexerTitle); + $commandTester->execute($command); + $actualValue = $commandTester->getDisplay(); + $this->assertEquals( + $consoleOutput, + $actualValue + ); + } + + /** + * @return array + */ + public function dimensionModesDataProvider(): array + { + return [ + 'was_changed' => [ + 'indexer_title' => 'indexer_title', + 'previousMode' => 'none', + 'command' => [ + 'indexer' => 'indexer_title', + 'mode' => 'store', + ], + 'output' => + sprintf( + 'Dimensions mode for indexer "%s" was changed from \'%s\' to \'%s\'', + 'indexer_title', + 'none', + 'store' + ) . PHP_EOL + , + ], + 'was_not_changed' => [ + 'indexer_title' => 'indexer_title', + 'previousMode' => 'none', + 'command' => [ + 'indexer' => 'indexer_title', + 'mode' => 'none', + ], + 'output' => + sprintf( + 'Dimensions mode for indexer "%s" has not been changed', + 'indexer_title' + ) . PHP_EOL + , + ], + ]; + } + + /** + * Tests indexer exception of method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute + * + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage + * Invalid value for "<indexer>" argument. Accepted values for "<indexer>" are 'indexer_title' + * @return void + */ + public function testExecuteWithIndxerException() + { + $commandTester = new CommandTester($this->command); + $this->indexerMock->method('getTitle')->willReturn('indexer_title'); + $commandTester->execute(['indexer' => 'non_existing_title']); + } + + /** + * Tests indexer exception of method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute + * + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Missing argument "<mode>". Accepted values for "<mode>" are 'store,website' + * @return void + */ + public function testExecuteWithModeException() + { + $commandTester = new CommandTester($this->command); + $this->dimensionModes->method('getDimensions')->willReturn([ + 'store' => 'dimension1', + 'website' => 'dimension2', + ]); + $this->dimensionModeSwitcherMock->method('getDimensionModes')->willReturn($this->dimensionModes); + $this->indexerMock->method('getTitle')->willReturn('indexer_title'); + $commandTester->execute([ + 'indexer' => 'indexer_title', + ]); + } + + /** + * Test execution of command without any arguments + * + * @return void + */ + public function testExecuteWithNoArguments() + { + $indexerTitle = 'indexer_title'; + $modesConfig = [ + 'store' => 'dimension1', + 'website' => 'dimension2', + ]; + $this->configureAdminArea(); + $commandTester = new CommandTester($this->command); + $this->indexerMock->method('getTitle')->willReturn($indexerTitle); + $this->dimensionModes->method('getDimensions')->willReturn($modesConfig); + $this->dimensionModeSwitcherMock->method('getDimensionModes')->willReturn($this->dimensionModes); + $commandTester->execute([]); + $actualValue = $commandTester->getDisplay(); + $consoleOutput = sprintf('%-50s', 'Indexer') . 'Available modes' . PHP_EOL + . sprintf('%-50s', $indexerTitle) . 'store,website' . PHP_EOL; + $this->assertEquals( + $consoleOutput, + $actualValue + ); + } +} diff --git a/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php new file mode 100644 index 0000000000000..f5487f268ac01 --- /dev/null +++ b/app/code/Magento/Indexer/Test/Unit/Console/Command/IndexerShowDimensionsModeCommandTest.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Indexer\Test\Unit\Console\Command; + +use Magento\Indexer\Console\Command\IndexerShowDimensionsModeCommand; +use Symfony\Component\Console\Tester\CommandTester; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class IndexerShowDimensionsModeCommandTest extends AbstractIndexerCommandCommonSetup +{ + /** + * Command being tested + * + * @var IndexerShowDimensionsModeCommand|\PHPUnit_Framework_MockObject_MockObject + */ + private $command; + + /** + * ScopeConfigInterface + * + * @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configReaderMock; + + /** + * @var \Magento\Indexer\Model\ModeSwitcherInterface[] + */ + private $indexers; + + /** + * @var \Magento\Indexer\Model\Indexer|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerMock; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $objectManagerHelper = new ObjectManagerHelper($this); + $this->configReaderMock = $this->createMock(ScopeConfigInterface::class); + $this->indexers = ['indexer_1' => 'indexer_1', 'indexer_2' => 'indexer_2']; + $this->command = $objectManagerHelper->getObject( + IndexerShowDimensionsModeCommand::class, + [ + 'objectManagerFactory' => $this->objectManagerFactory, + 'configReader' => $this->configReaderMock, + 'indexers' => $this->indexers, + ] + ); + } + + /** + * Get return value map for object manager + * + * @return array + */ + protected function getObjectManagerReturnValueMap(): array + { + $result = parent::getObjectManagerReturnValueMap(); + $this->indexerMock = $this->createMock(\Magento\Indexer\Model\Indexer::class); + $result[] = [\Magento\Indexer\Model\Indexer::class, $this->indexerMock]; + + return $result; + } + + /** + * Tests method \Magento\Indexer\Console\Command\IndexerDimensionsModeCommand::execute + * + * @param $command + * @param $consoleOutput + * @dataProvider dimensionModesDataProvider + */ + public function testExecuteWithAttributes($command, $consoleOutput) + { + $indexers = [['indexer_1'], ['indexer_2']]; + $indexerTitles = ['indexer_title1', 'indexer_title2']; + $this->configureAdminArea(); + /** @var CommandTester $commandTester */ + $commandTester = new CommandTester($this->command); + $this->indexerMock->method('load')->withConsecutive(...$indexers); + $this->indexerMock->method('getTitle')->willReturnOnConsecutiveCalls(...$indexerTitles); + $commandTester->execute($command); + $actualValue = $commandTester->getDisplay(); + $this->assertEquals( + $consoleOutput, + $actualValue + ); + } + + /** + * @return array + */ + public function dimensionModesDataProvider(): array + { + return [ + 'get_all' => [ + 'command' => [], + 'output' => + sprintf( + '%-50s ', + 'indexer_title1' . ':' + ) . 'none' . PHP_EOL . + sprintf( + '%-50s ', + 'indexer_title2' . ':' + ) . 'none' . PHP_EOL + , + ], + 'get_by_index' => [ + 'command' => [ + 'indexer' => ['indexer_1'], + ], + 'output' => + sprintf( + '%-50s ', + 'indexer_title1' . ':' + ) . 'none' . PHP_EOL + , + ], + 'get_by_several_indexes' => [ + 'command' => [ + 'indexer' => ['indexer_1', 'indexer_2'], + ], + 'output' => + sprintf( + '%-50s ', + 'indexer_title1' . ':' + ) . 'none' . PHP_EOL . + sprintf( + '%-50s ', + 'indexer_title2' . ':' + ) . 'none' . PHP_EOL + , + ], + ]; + } +} diff --git a/app/code/Magento/Indexer/composer.json b/app/code/Magento/Indexer/composer.json index a884ca1ca5f0d..1667f8a05a5e8 100644 --- a/app/code/Magento/Indexer/composer.json +++ b/app/code/Magento/Indexer/composer.json @@ -7,7 +7,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Indexer/etc/di.xml b/app/code/Magento/Indexer/etc/di.xml index 6abaaf625e108..c7603191e8606 100644 --- a/app/code/Magento/Indexer/etc/di.xml +++ b/app/code/Magento/Indexer/etc/di.xml @@ -56,6 +56,8 @@ <item name="show-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerShowModeCommand</item> <item name="status" xsi:type="object">Magento\Indexer\Console\Command\IndexerStatusCommand</item> <item name="reset" xsi:type="object">Magento\Indexer\Console\Command\IndexerResetStateCommand</item> + <item name="set-dimensions-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand</item> + <item name="show-dimensions-mode" xsi:type="object">Magento\Indexer\Console\Command\IndexerShowDimensionsModeCommand</item> </argument> </arguments> </type> diff --git a/app/code/Magento/InstantPurchase/Model/ShippingMethodChoose/CarrierFinder.php b/app/code/Magento/InstantPurchase/Model/ShippingMethodChoose/CarrierFinder.php index 0eb2afd04140a..d3e84083e5b42 100644 --- a/app/code/Magento/InstantPurchase/Model/ShippingMethodChoose/CarrierFinder.php +++ b/app/code/Magento/InstantPurchase/Model/ShippingMethodChoose/CarrierFinder.php @@ -11,7 +11,7 @@ use Magento\Store\Model\StoreManagerInterface; /** - * Collect shipping rates for customer address without packaging estiamtion. + * Collect shipping rates for customer address without packaging estimation. */ class CarrierFinder { diff --git a/app/code/Magento/InstantPurchase/Test/Mftf/composer.json b/app/code/Magento/InstantPurchase/Test/Mftf/composer.json deleted file mode 100644 index 4ef7c0e89b8c3..0000000000000 --- a/app/code/Magento/InstantPurchase/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-instant-purchase", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-vault": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/InstantPurchase/composer.json b/app/code/Magento/InstantPurchase/composer.json index 6ec2364232169..cd7684254e408 100644 --- a/app/code/Magento/InstantPurchase/composer.json +++ b/app/code/Magento/InstantPurchase/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-instant-purchase", "description": "N/A", "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Integration/Model/Config/Consolidated/Converter.php b/app/code/Magento/Integration/Model/Config/Consolidated/Converter.php index 674a97a68d065..a2fbd0ffbb496 100644 --- a/app/code/Magento/Integration/Model/Config/Consolidated/Converter.php +++ b/app/code/Magento/Integration/Model/Config/Consolidated/Converter.php @@ -80,9 +80,16 @@ public function convert($source) $result[$integrationName][self::API_RESOURCES][] = $name; } } + + // Add root resource if any child has been added + if (!empty($result[$integrationName][self::API_RESOURCES])) { + array_unshift($result[$integrationName][self::API_RESOURCES], $allResources[1]['id']); + } + // Remove any duplicates added parents - $result[$integrationName][self::API_RESOURCES] = - array_values(array_unique($result[$integrationName][self::API_RESOURCES])); + $result[$integrationName][self::API_RESOURCES] = array_values( + array_unique($result[$integrationName][self::API_RESOURCES]) + ); } return $result; } diff --git a/app/code/Magento/Integration/Model/CustomerTokenService.php b/app/code/Magento/Integration/Model/CustomerTokenService.php index 947ca6f9f8821..adacf5ebacf71 100644 --- a/app/code/Magento/Integration/Model/CustomerTokenService.php +++ b/app/code/Magento/Integration/Model/CustomerTokenService.php @@ -14,6 +14,7 @@ use Magento\Integration\Model\ResourceModel\Oauth\Token\CollectionFactory as TokenCollectionFactory; use Magento\Integration\Model\Oauth\Token\RequestThrottler; use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\Event\ManagerInterface; class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServiceInterface { @@ -48,6 +49,11 @@ class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServ */ private $requestThrottler; + /** + * @var Magento\Framework\Event\ManagerInterface + */ + private $eventManager; + /** * Initialize service * @@ -55,17 +61,21 @@ class CustomerTokenService implements \Magento\Integration\Api\CustomerTokenServ * @param AccountManagementInterface $accountManagement * @param TokenCollectionFactory $tokenModelCollectionFactory * @param \Magento\Integration\Model\CredentialsValidator $validatorHelper + * @param \Magento\Framework\Event\ManagerInterface $eventManager */ public function __construct( TokenModelFactory $tokenModelFactory, AccountManagementInterface $accountManagement, TokenCollectionFactory $tokenModelCollectionFactory, - CredentialsValidator $validatorHelper + CredentialsValidator $validatorHelper, + ManagerInterface $eventManager = null ) { $this->tokenModelFactory = $tokenModelFactory; $this->accountManagement = $accountManagement; $this->tokenModelCollectionFactory = $tokenModelCollectionFactory; $this->validatorHelper = $validatorHelper; + $this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(ManagerInterface::class); } /** @@ -83,6 +93,7 @@ public function createCustomerAccessToken($username, $password) __('You did not sign in correctly or your account is temporarily disabled.') ); } + $this->eventManager->dispatch('customer_login', ['customer' => $customerDataObject]); $this->getRequestThrottler()->resetAuthenticationFailuresCount($username, RequestThrottler::USER_TYPE_CUSTOMER); return $this->tokenModelFactory->create()->createCustomerToken($customerDataObject->getId())->getToken(); } diff --git a/app/code/Magento/Integration/Test/Mftf/composer.json b/app/code/Magento/Integration/Test/Mftf/composer.json deleted file mode 100644 index 37d3cfd3bfaf4..0000000000000 --- a/app/code/Magento/Integration/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-integration", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev", - "magento/functional-test-module-security": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-authorization": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/acl.php b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/acl.php index 5b7bbace5a65d..42b62acaba5b4 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/acl.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/acl.php @@ -6,6 +6,8 @@ return [ [], [ + 'id' => 'Magento_Backend::admin', + 'title' => 'Magento Admin (Root)', 'children' => [ [ diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.php b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.php index 54e05d5ef9017..0293492c77658 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.php +++ b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.php @@ -9,6 +9,7 @@ 'endpoint_url' => 'http://endpoint.com', 'identity_link_url' => 'http://www.example.com/identity', 'resource' => [ + 'Magento_Backend::admin', 'Magento_Customer::manageParent', 'Magento_Customer::manage', 'Magento_SalesRule::quoteParent', @@ -17,6 +18,9 @@ ], 'TestIntegration2' => [ 'email' => 'test-integration2@magento.com', - 'resource' => ['Magento_Sales::sales'] + 'resource' => [ + 'Magento_Backend::admin', + 'Magento_Sales::sales' + ] ] ]; diff --git a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.xml b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.xml index 585fabc24299d..f8bcf3fc4a2ce 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.xml +++ b/app/code/Magento/Integration/Test/Unit/Model/Config/Consolidated/_files/integration.xml @@ -18,6 +18,7 @@ <integration name="TestIntegration2"> <email>test-integration2@magento.com</email> <resources> + <resource name="Magento_Backend::admin" /> <resource name="Magento_Sales::sales" /> </resources> </integration> diff --git a/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php b/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php index ecd4788545c0a..170e7e42d919e 100644 --- a/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php +++ b/app/code/Magento/Integration/Test/Unit/Model/CustomerTokenServiceTest.php @@ -32,6 +32,9 @@ class CustomerTokenServiceTest extends \PHPUnit\Framework\TestCase /** @var \Magento\Integration\Model\Oauth\Token|\PHPUnit_Framework_MockObject_MockObject */ private $_tokenMock; + /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ + protected $manager; + protected function setUp() { $this->_tokenFactoryMock = $this->getMockBuilder(\Magento\Integration\Model\Oauth\TokenFactory::class) @@ -67,11 +70,14 @@ protected function setUp() \Magento\Integration\Model\CredentialsValidator::class )->disableOriginalConstructor()->getMock(); + $this->manager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class); + $this->_tokenService = new \Magento\Integration\Model\CustomerTokenService( $this->_tokenFactoryMock, $this->_accountManagementMock, $this->_tokenModelCollectionFactoryMock, - $this->validatorHelperMock + $this->validatorHelperMock, + $this->manager ); } diff --git a/app/code/Magento/Integration/composer.json b/app/code/Magento/Integration/composer.json index 4bc2269af9822..75bd7a04d0dcc 100644 --- a/app/code/Magento/Integration/composer.json +++ b/app/code/Magento/Integration/composer.json @@ -12,7 +12,7 @@ "magento/module-authorization": "100.2.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Integration/etc/adminhtml/system.xml b/app/code/Magento/Integration/etc/adminhtml/system.xml index 5abec8efbfdd6..fe80fe105493a 100644 --- a/app/code/Magento/Integration/etc/adminhtml/system.xml +++ b/app/code/Magento/Integration/etc/adminhtml/system.xml @@ -54,7 +54,7 @@ <label>Maximum Login Failures to Lock Out Account</label> <comment>Maximum Number of authentication failures to lock out account.</comment> </field> - <field id="timeout" translate="label" type="text comment" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> + <field id="timeout" translate="label comment" type="text" sortOrder="30" showInDefault="1" showInWebsite="0" showInStore="0" canRestore="1"> <label>Lockout Time (seconds)</label> <comment>Period of time in seconds after which account will be unlocked.</comment> </field> diff --git a/app/code/Magento/Integration/etc/webapi_rest/events.xml b/app/code/Magento/Integration/etc/webapi_rest/events.xml new file mode 100644 index 0000000000000..e978698734277 --- /dev/null +++ b/app/code/Magento/Integration/etc/webapi_rest/events.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="customer_login"> + <observer name="customer_log_login" instance="Magento\Customer\Observer\LogLastLoginAtObserver" /> + </event> +</config> diff --git a/app/code/Magento/Integration/etc/webapi_soap/events.xml b/app/code/Magento/Integration/etc/webapi_soap/events.xml new file mode 100644 index 0000000000000..e978698734277 --- /dev/null +++ b/app/code/Magento/Integration/etc/webapi_soap/events.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd"> + <event name="customer_login"> + <observer name="customer_log_login" instance="Magento\Customer\Observer\LogLastLoginAtObserver" /> + </event> +</config> diff --git a/app/code/Magento/LayeredNavigation/Observer/Edit/Tab/Front/ProductAttributeFormBuildFrontTabObserver.php b/app/code/Magento/LayeredNavigation/Observer/Edit/Tab/Front/ProductAttributeFormBuildFrontTabObserver.php index 6b64610caf5ed..fc1edeb1e2392 100644 --- a/app/code/Magento/LayeredNavigation/Observer/Edit/Tab/Front/ProductAttributeFormBuildFrontTabObserver.php +++ b/app/code/Magento/LayeredNavigation/Observer/Edit/Tab/Front/ProductAttributeFormBuildFrontTabObserver.php @@ -54,8 +54,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) [ 'name' => 'is_filterable', 'label' => __("Use in Layered Navigation"), - 'title' => __('Can be used only with catalog input type Dropdown, Multiple Select and Price'), - 'note' => __('Can be used only with catalog input type Dropdown, Multiple Select and Price.'), + 'title' => __('Can be used only with catalog input type Yes/No, Dropdown, Multiple Select and Price'), + 'note' => __('Can be used only with catalog input type Yes/No, Dropdown, Multiple Select and Price.'), 'values' => [ ['value' => '0', 'label' => __('No')], ['value' => '1', 'label' => __('Filterable (with results)')], @@ -70,8 +70,8 @@ public function execute(\Magento\Framework\Event\Observer $observer) [ 'name' => 'is_filterable_in_search', 'label' => __("Use in Search Results Layered Navigation"), - 'title' => __('Can be used only with catalog input type Dropdown, Multiple Select and Price'), - 'note' => __('Can be used only with catalog input type Dropdown, Multiple Select and Price.'), + 'title' => __('Can be used only with catalog input type Yes/No, Dropdown, Multiple Select and Price'), + 'note' => __('Can be used only with catalog input type Yes/No, Dropdown, Multiple Select and Price.'), 'values' => $this->optionList->toOptionArray(), ] ); diff --git a/app/code/Magento/LayeredNavigation/Test/Mftf/composer.json b/app/code/Magento/LayeredNavigation/Test/Mftf/composer.json deleted file mode 100644 index 9527ef51e8a32..0000000000000 --- a/app/code/Magento/LayeredNavigation/Test/Mftf/composer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "magento/functional-test-module-layered-navigation", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/LayeredNavigation/composer.json b/app/code/Magento/LayeredNavigation/composer.json index 23d6c79c58443..b8dfc77532784 100644 --- a/app/code/Magento/LayeredNavigation/composer.json +++ b/app/code/Magento/LayeredNavigation/composer.json @@ -8,7 +8,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/LayeredNavigation/view/adminhtml/ui_component/product_attribute_add_form.xml b/app/code/Magento/LayeredNavigation/view/adminhtml/ui_component/product_attribute_add_form.xml index 000e10197af1b..0d7476081d19e 100644 --- a/app/code/Magento/LayeredNavigation/view/adminhtml/ui_component/product_attribute_add_form.xml +++ b/app/code/Magento/LayeredNavigation/view/adminhtml/ui_component/product_attribute_add_form.xml @@ -11,6 +11,7 @@ <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="valuesForEnable" xsi:type="array"> + <item name="boolean" xsi:type="string">boolean</item> <item name="select" xsi:type="string">select</item> <item name="multiselect" xsi:type="string">multiselect</item> <item name="price" xsi:type="string">price</item> @@ -19,7 +20,7 @@ </item> </argument> <settings> - <notice translate="true">Can be used only with catalog input type Dropdown, Multiple Select and Price.</notice> + <notice translate="true">Can be used only with catalog input type Yes/No (Boolean), Dropdown, Multiple Select and Price.</notice> <dataType>string</dataType> <label translate="true">Use in Layered Navigation</label> <dataScope>is_filterable</dataScope> @@ -36,6 +37,7 @@ <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="valuesForEnable" xsi:type="array"> + <item name="boolean" xsi:type="string">boolean</item> <item name="select" xsi:type="string">select</item> <item name="multiselect" xsi:type="string">multiselect</item> <item name="price" xsi:type="string">price</item> @@ -45,7 +47,7 @@ </item> </argument> <settings> - <notice translate="true">Can be used only with catalog input type Dropdown, Multiple Select and Price.</notice> + <notice translate="true">Can be used only with catalog input type Yes/No (Boolean), Dropdown, Multiple Select and Price.</notice> <label translate="true">Use in Search Results Layered Navigation</label> <dataScope>is_filterable_in_search</dataScope> <imports> diff --git a/app/code/Magento/Marketplace/Test/Mftf/composer.json b/app/code/Magento/Marketplace/Test/Mftf/composer.json deleted file mode 100644 index 4ecbcb9bb1c9a..0000000000000 --- a/app/code/Magento/Marketplace/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-marketplace", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Marketplace/composer.json b/app/code/Magento/Marketplace/composer.json index 951b51c474e24..5ce8666ec1bb2 100644 --- a/app/code/Magento/Marketplace/composer.json +++ b/app/code/Magento/Marketplace/composer.json @@ -7,7 +7,7 @@ "magento/module-backend": "100.2.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/MediaStorage/Test/Mftf/composer.json b/app/code/Magento/MediaStorage/Test/Mftf/composer.json deleted file mode 100644 index 324969f454ab4..0000000000000 --- a/app/code/Magento/MediaStorage/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-media-storage", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/MediaStorage/composer.json b/app/code/Magento/MediaStorage/composer.json index a06d12add7b9f..afb290a682274 100644 --- a/app/code/Magento/MediaStorage/composer.json +++ b/app/code/Magento/MediaStorage/composer.json @@ -9,7 +9,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Msrp/Test/Mftf/composer.json b/app/code/Magento/Msrp/Test/Mftf/composer.json deleted file mode 100644 index a4f70d6845f01..0000000000000 --- a/app/code/Magento/Msrp/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-msrp", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-grouped-product": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-msrp-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Msrp/composer.json b/app/code/Magento/Msrp/composer.json index 05aee8b3ffe53..97bc64e48ce01 100644 --- a/app/code/Magento/Msrp/composer.json +++ b/app/code/Magento/Msrp/composer.json @@ -16,7 +16,7 @@ "magento/module-msrp-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Multishipping/Helper/Data.php b/app/code/Magento/Multishipping/Helper/Data.php index c752057ebe7f5..8abf91d2105a0 100644 --- a/app/code/Magento/Multishipping/Helper/Data.php +++ b/app/code/Magento/Multishipping/Helper/Data.php @@ -13,16 +13,19 @@ */ class Data extends \Magento\Framework\App\Helper\AbstractHelper { - /**#@+ + /* * Xml paths for multishipping checkout + * **/ const XML_PATH_CHECKOUT_MULTIPLE_AVAILABLE = 'multishipping/options/checkout_multiple'; const XML_PATH_CHECKOUT_MULTIPLE_MAXIMUM_QUANTITY = 'multishipping/options/checkout_multiple_maximum_qty'; - /**#@-*/ - - /**#@-*/ + /** + * Checkout session + * + * @var \Magento\Checkout\Model\Session + */ protected $checkoutSession; /** diff --git a/app/code/Magento/Multishipping/Test/Mftf/composer.json b/app/code/Magento/Multishipping/Test/Mftf/composer.json deleted file mode 100644 index d2945d1e00e39..0000000000000 --- a/app/code/Magento/Multishipping/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-multishipping", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Multishipping/composer.json b/app/code/Magento/Multishipping/composer.json index 9fd762d631657..111204915e4c8 100644 --- a/app/code/Magento/Multishipping/composer.json +++ b/app/code/Magento/Multishipping/composer.json @@ -15,7 +15,7 @@ "magento/module-theme": "100.2.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/NewRelicReporting/Model/Config.php b/app/code/Magento/NewRelicReporting/Model/Config.php index bcc87ec72d53f..4bb381eb2f12d 100644 --- a/app/code/Magento/NewRelicReporting/Model/Config.php +++ b/app/code/Magento/NewRelicReporting/Model/Config.php @@ -5,6 +5,9 @@ */ namespace Magento\NewRelicReporting\Model; +/** + * NewRelic configuration model + */ class Config { /**#@+ diff --git a/app/code/Magento/NewRelicReporting/Model/Cron/ReportModulesInfo.php b/app/code/Magento/NewRelicReporting/Model/Cron/ReportModulesInfo.php index 9cdc90bc46b2a..78c485c5bb6f5 100644 --- a/app/code/Magento/NewRelicReporting/Model/Cron/ReportModulesInfo.php +++ b/app/code/Magento/NewRelicReporting/Model/Cron/ReportModulesInfo.php @@ -64,6 +64,7 @@ public function report() $moduleData = $this->collect->getModuleData(); if (count($moduleData['changes']) > 0) { foreach ($moduleData['changes'] as $change) { + $modelData = []; switch ($change['type']) { case Config::ENABLED: $modelData = [ diff --git a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php index ec21e06976b8b..9882a1ce9b0b8 100644 --- a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php +++ b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php @@ -31,7 +31,7 @@ public function addCustomParameter($param, $value) /** * Wrapper for 'newrelic_notice_error' function * - * @param Exception $exception + * @param \Exception $exception * @return void */ public function reportError($exception) diff --git a/app/code/Magento/NewRelicReporting/Plugin/StatePlugin.php b/app/code/Magento/NewRelicReporting/Plugin/StatePlugin.php index d84f278bb1c68..8be29fa6db9d9 100644 --- a/app/code/Magento/NewRelicReporting/Plugin/StatePlugin.php +++ b/app/code/Magento/NewRelicReporting/Plugin/StatePlugin.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\NewRelicReporting\Plugin; use Magento\Framework\App\State; @@ -11,6 +13,9 @@ use Magento\NewRelicReporting\Model\NewRelicWrapper; use Psr\Log\LoggerInterface; +/** + * Handles setting which, when enabled, reports frontend and adminhtml as separate apps to New Relic. + */ class StatePlugin { /** @@ -31,6 +36,7 @@ class StatePlugin /** * @param Config $config * @param NewRelicWrapper $newRelicWrapper + * @param LoggerInterface $logger */ public function __construct( Config $config, @@ -46,32 +52,33 @@ public function __construct( * Set separate appname * * @param State $subject - * @param null $result - * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param mixed $result + * @return mixed */ - public function afterSetAreaCode(State $state, $result) + public function afterSetAreaCode(State $subject, $result) { if (!$this->shouldSetAppName()) { return $result; } try { - $this->newRelicWrapper->setAppName($this->appName($state)); + $this->newRelicWrapper->setAppName($this->appName($subject)); } catch (LocalizedException $e) { $this->logger->critical($e); return $result; } + + return $result; } /** - * @param State $state + * Format appName. * + * @param State $state * @return string * @throws LocalizedException */ - private function appName(State $state) + private function appName(State $state): string { $code = $state->getAreaCode(); $current = $this->config->getNewRelicAppName(); @@ -80,22 +87,16 @@ private function appName(State $state) } /** + * Check if app name should be set. + * * @return bool */ - private function shouldSetAppName() + private function shouldSetAppName(): bool { - if (!$this->config->isNewRelicEnabled()) { - return false; - } - - if (!$this->config->getNewRelicAppName()) { - return false; - } - - if (!$this->config->isSeparateApps()) { - return false; - } - - return true; + return ( + $this->config->isSeparateApps() && + $this->config->getNewRelicAppName() && + $this->config->isNewRelicEnabled() + ); } } diff --git a/app/code/Magento/NewRelicReporting/Test/Mftf/composer.json b/app/code/Magento/NewRelicReporting/Test/Mftf/composer.json deleted file mode 100644 index 8beaf8be7750c..0000000000000 --- a/app/code/Magento/NewRelicReporting/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-new-relic-reporting", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/NewRelicReporting/composer.json b/app/code/Magento/NewRelicReporting/composer.json index d8bed94a368fa..18a1a2df57336 100644 --- a/app/code/Magento/NewRelicReporting/composer.json +++ b/app/code/Magento/NewRelicReporting/composer.json @@ -13,7 +13,7 @@ "magento/magento-composer-installer": "*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php b/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php index 13ab40665e591..fd2a61702e909 100644 --- a/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php +++ b/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php @@ -10,19 +10,32 @@ use Magento\Customer\Model\Session; use Magento\Customer\Model\Url as CustomerUrl; use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; +use Magento\Framework\Validator\EmailAddress as EmailValidator; +use Magento\Newsletter\Controller\Subscriber as SubscriberController; +use Magento\Newsletter\Model\Subscriber; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\StoreManagerInterface; use Magento\Newsletter\Model\SubscriberFactory; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class NewAction extends \Magento\Newsletter\Controller\Subscriber +class NewAction extends SubscriberController { /** * @var CustomerAccountManagement */ protected $customerAccountManagement; + /** + * @var EmailValidator + */ + private $emailValidator; + /** * Initialize dependencies. * @@ -32,6 +45,7 @@ class NewAction extends \Magento\Newsletter\Controller\Subscriber * @param StoreManagerInterface $storeManager * @param CustomerUrl $customerUrl * @param CustomerAccountManagement $customerAccountManagement + * @param EmailValidator $emailValidator */ public function __construct( Context $context, @@ -39,9 +53,11 @@ public function __construct( Session $customerSession, StoreManagerInterface $storeManager, CustomerUrl $customerUrl, - CustomerAccountManagement $customerAccountManagement + CustomerAccountManagement $customerAccountManagement, + EmailValidator $emailValidator = null ) { $this->customerAccountManagement = $customerAccountManagement; + $this->emailValidator = $emailValidator ?: ObjectManager::getInstance()->get(EmailValidator::class); parent::__construct( $context, $subscriberFactory, @@ -55,7 +71,7 @@ public function __construct( * Validates that the email address isn't being used by a different account. * * @param string $email - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @return void */ protected function validateEmailAvailable($email) @@ -64,7 +80,7 @@ protected function validateEmailAvailable($email) if ($this->_customerSession->getCustomerDataObject()->getEmail() !== $email && !$this->customerAccountManagement->isEmailAvailable($email, $websiteId) ) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('This email address is already assigned to another user.') ); } @@ -73,19 +89,19 @@ protected function validateEmailAvailable($email) /** * Validates that if the current user is a guest, that they can subscribe to a newsletter. * - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @return void */ protected function validateGuestSubscription() { - if ($this->_objectManager->get(\Magento\Framework\App\Config\ScopeConfigInterface::class) + if ($this->_objectManager->get(ScopeConfigInterface::class) ->getValue( - \Magento\Newsletter\Model\Subscriber::XML_PATH_ALLOW_GUEST_SUBSCRIBE_FLAG, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE + Subscriber::XML_PATH_ALLOW_GUEST_SUBSCRIBE_FLAG, + ScopeInterface::SCOPE_STORE ) != 1 && !$this->_customerSession->isLoggedIn() ) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __( 'Sorry, but the administrator denied subscription for guests. Please <a href="%1">register</a>.', $this->_customerUrl->getRegisterUrl() @@ -98,20 +114,19 @@ protected function validateGuestSubscription() * Validates the format of the email address * * @param string $email - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @return void */ protected function validateEmailFormat($email) { - if (!\Zend_Validate::is($email, \Magento\Framework\Validator\EmailAddress::class)) { - throw new \Magento\Framework\Exception\LocalizedException(__('Please enter a valid email address.')); + if (!$this->emailValidator->isValid($email)) { + throw new LocalizedException(__('Please enter a valid email address.')); } } /** * New subscription action * - * @throws \Magento\Framework\Exception\LocalizedException * @return void */ public function execute() @@ -126,28 +141,37 @@ public function execute() $subscriber = $this->_subscriberFactory->create()->loadByEmail($email); if ($subscriber->getId() - && $subscriber->getSubscriberStatus() == \Magento\Newsletter\Model\Subscriber::STATUS_SUBSCRIBED + && (int) $subscriber->getSubscriberStatus() === Subscriber::STATUS_SUBSCRIBED ) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('This email address is already subscribed.') ); } - $status = $this->_subscriberFactory->create()->subscribe($email); - if ($status == \Magento\Newsletter\Model\Subscriber::STATUS_NOT_ACTIVE) { - $this->messageManager->addSuccess(__('The confirmation request has been sent.')); - } else { - $this->messageManager->addSuccess(__('Thank you for your subscription.')); - } - } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addException( + $status = (int) $this->_subscriberFactory->create()->subscribe($email); + $this->messageManager->addSuccessMessage($this->getSuccessMessage($status)); + } catch (LocalizedException $e) { + $this->messageManager->addExceptionMessage( $e, __('There was a problem with the subscription: %1', $e->getMessage()) ); } catch (\Exception $e) { - $this->messageManager->addException($e, __('Something went wrong with the subscription.')); + $this->messageManager->addExceptionMessage($e, __('Something went wrong with the subscription.')); } } $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl()); } + + /** + * @param int $status + * @return Phrase + */ + private function getSuccessMessage(int $status): Phrase + { + if ($status === Subscriber::STATUS_NOT_ACTIVE) { + return __('The confirmation request has been sent.'); + } + + return __('Thank you for your subscription.'); + } } diff --git a/app/code/Magento/Newsletter/Model/Subscriber.php b/app/code/Magento/Newsletter/Model/Subscriber.php index b916f20dbe770..5e0d5448f11ca 100644 --- a/app/code/Magento/Newsletter/Model/Subscriber.php +++ b/app/code/Magento/Newsletter/Model/Subscriber.php @@ -594,6 +594,8 @@ protected function _updateCustomerSubscription($customerId, $subscribe) } elseif (($this->getStatus() == self::STATUS_UNCONFIRMED) && ($customerData->getConfirmation() === null)) { $status = self::STATUS_SUBSCRIBED; $sendInformationEmail = true; + } elseif (($this->getStatus() == self::STATUS_NOT_ACTIVE) && ($customerData->getConfirmation() === null)) { + $status = self::STATUS_NOT_ACTIVE; } else { $status = self::STATUS_UNSUBSCRIBED; } diff --git a/app/code/Magento/Newsletter/Test/Mftf/composer.json b/app/code/Magento/Newsletter/Test/Mftf/composer.json deleted file mode 100644 index aee1960a57fa0..0000000000000 --- a/app/code/Magento/Newsletter/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-newsletter", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-require-js": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Newsletter/composer.json b/app/code/Magento/Newsletter/composer.json index 5b3a4ae3561f7..ef6158960aeee 100644 --- a/app/code/Magento/Newsletter/composer.json +++ b/app/code/Magento/Newsletter/composer.json @@ -14,7 +14,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/OfflinePayments/Test/Mftf/composer.json b/app/code/Magento/OfflinePayments/Test/Mftf/composer.json deleted file mode 100644 index 10138656adbc6..0000000000000 --- a/app/code/Magento/OfflinePayments/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-offline-payments", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/OfflinePayments/composer.json b/app/code/Magento/OfflinePayments/composer.json index b84a064960d7e..b737149d30a94 100644 --- a/app/code/Magento/OfflinePayments/composer.json +++ b/app/code/Magento/OfflinePayments/composer.json @@ -11,7 +11,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php b/app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php index b546237b82565..2373b5285ed00 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Freeshipping.php @@ -97,8 +97,18 @@ public function collectRates(RateRequest $request) $method->setCost('0.00'); $result->append($method); + } elseif ($this->getConfigData('showmethod')) { + $error = $this->_rateErrorFactory->create(); + $error->setCarrier($this->_code); + $error->setCarrierTitle($this->getConfigData('title')); + $errorMsg = $this->getConfigData('specificerrmsg'); + $error->setErrorMessage( + $errorMsg ? $errorMsg : __( + 'Sorry, but we can\'t deliver to the destination country with this shipping module.' + ) + ); + return $error; } - return $result; } diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php index b67eb1b45fe45..cead9f838f4c0 100644 --- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php +++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php @@ -60,6 +60,7 @@ class Tablerate extends \Magento\Shipping\Model\Carrier\AbstractCarrier implemen * @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $resultMethodFactory * @param \Magento\OfflineShipping\Model\ResourceModel\Carrier\TablerateFactory $tablerateFactory * @param array $data + * @throws LocalizedException * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function __construct( diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php index 4d2e11ebb8a1e..9b6f37f1a04e4 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/CSV/RowParser.php @@ -9,6 +9,9 @@ use Magento\Framework\Phrase; use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\LocationDirectory; +/** + * Row parser. + */ class RowParser { /** @@ -26,6 +29,8 @@ public function __construct(LocationDirectory $locationDirectory) } /** + * Retrieve columns. + * * @return array */ public function getColumns() @@ -42,6 +47,8 @@ public function getColumns() } /** + * Parse provided row data. + * * @param array $rowData * @param int $rowNumber * @param int $websiteId @@ -62,27 +69,39 @@ public function parse( ) { // validate row if (count($rowData) < 5) { - throw new RowException(__('Please correct Table Rates format in the Row #%1.', $rowNumber)); + throw new RowException( + __( + 'The Table Rates File Format is incorrect in row number "%1". Verify the format and try again.', + $rowNumber + ) + ); } $countryId = $this->getCountryId($rowData, $rowNumber, $columnResolver); - $regionId = $this->getRegionId($rowData, $rowNumber, $columnResolver, $countryId); + $regionIds = $this->getRegionIds($rowData, $rowNumber, $columnResolver, $countryId); $zipCode = $this->getZipCode($rowData, $columnResolver); $conditionValue = $this->getConditionValue($rowData, $rowNumber, $conditionFullName, $columnResolver); $price = $this->getPrice($rowData, $rowNumber, $columnResolver); - return [ - 'website_id' => $websiteId, - 'dest_country_id' => $countryId, - 'dest_region_id' => $regionId, - 'dest_zip' => $zipCode, - 'condition_name' => $conditionShortName, - 'condition_value' => $conditionValue, - 'price' => $price, - ]; + $rates = []; + foreach ($regionIds as $regionId) { + $rates[] = [ + 'website_id' => $websiteId, + 'dest_country_id' => $countryId, + 'dest_region_id' => $regionId, + 'dest_zip' => $zipCode, + 'condition_name' => $conditionShortName, + 'condition_value' => $conditionValue, + 'price' => $price, + ]; + } + + return $rates; } /** + * Get country id from provided row data. + * * @param array $rowData * @param int $rowNumber * @param ColumnResolver $columnResolver @@ -99,34 +118,53 @@ private function getCountryId(array $rowData, $rowNumber, ColumnResolver $column } elseif ($countryCode === '*' || $countryCode === '') { $countryId = '0'; } else { - throw new RowException(__('Please correct Country "%1" in the Row #%2.', $countryCode, $rowNumber)); + throw new RowException( + __( + 'The "%1" country in row number "%2" is incorrect. Verify the country and try again.', + $countryCode, + $rowNumber + ) + ); } + return $countryId; } /** + * Retrieve region id from provided row data. + * * @param array $rowData * @param int $rowNumber * @param ColumnResolver $columnResolver * @param int $countryId - * @return int|string + * @return array * @throws ColumnNotFoundException * @throws RowException */ - private function getRegionId(array $rowData, $rowNumber, ColumnResolver $columnResolver, $countryId) + private function getRegionIds(array $rowData, $rowNumber, ColumnResolver $columnResolver, $countryId): array { $regionCode = $columnResolver->getColumnValue(ColumnResolver::COLUMN_REGION, $rowData); if ($countryId !== '0' && $this->locationDirectory->hasRegionId($countryId, $regionCode)) { - $regionId = $this->locationDirectory->getRegionId($countryId, $regionCode); + $regionIds = $this->locationDirectory->getRegionIds($countryId, $regionCode); } elseif ($regionCode === '*' || $regionCode === '') { - $regionId = 0; + $regionIds = [0]; } else { - throw new RowException(__('Please correct Region/State "%1" in the Row #%2.', $regionCode, $rowNumber)); + throw new RowException( + __( + 'The "%1" region or state in row number "%2" is incorrect. ' + . 'Verify the region or state and try again.', + $regionCode, + $rowNumber + ) + ); } - return $regionId; + + return $regionIds; } /** + * Retrieve zip code from provided row data. + * * @param array $rowData * @param ColumnResolver $columnResolver * @return float|int|null|string @@ -138,10 +176,13 @@ private function getZipCode(array $rowData, ColumnResolver $columnResolver) if ($zipCode === '') { $zipCode = '*'; } + return $zipCode; } /** + * Get condition value form provided row data. + * * @param array $rowData * @param int $rowNumber * @param string $conditionFullName @@ -165,10 +206,13 @@ private function getConditionValue(array $rowData, $rowNumber, $conditionFullNam ) ); } + return $value; } /** + * Retrieve price from provided row data. + * * @param array $rowData * @param int $rowNumber * @param ColumnResolver $columnResolver @@ -181,13 +225,21 @@ private function getPrice(array $rowData, $rowNumber, ColumnResolver $columnReso $priceValue = $columnResolver->getColumnValue(ColumnResolver::COLUMN_PRICE, $rowData); $price = $this->_parseDecimalValue($priceValue); if ($price === false) { - throw new RowException(__('Please correct Shipping Price "%1" in the Row #%2.', $priceValue, $rowNumber)); + throw new RowException( + __( + 'The "%1" shipping price in row number "%2" is incorrect. Verify the shipping price and try again.', + $priceValue, + $rowNumber + ) + ); } + return $price; } /** * Parse and validate positive decimal value + * * Return false if value is not decimal or is not positive * * @param string $value @@ -202,6 +254,7 @@ private function _parseDecimalValue($value) $result = $value; } } + return $result; } } diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php index 1012394f22fcb..1669364b03f1d 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/Import.php @@ -16,6 +16,11 @@ use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate\CSV\RowParser; use Magento\Store\Model\StoreManagerInterface; +/** + * Import offline shipping. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class Import { /** @@ -84,6 +89,8 @@ public function __construct( } /** + * Check if there are errors. + * * @return bool */ public function hasErrors() @@ -92,6 +99,8 @@ public function hasErrors() } /** + * Get errors. + * * @return array */ public function getErrors() @@ -100,6 +109,8 @@ public function getErrors() } /** + * Retrieve columns. + * * @return array */ public function getColumns() @@ -108,6 +119,8 @@ public function getColumns() } /** + * Get data from file. + * * @param ReadInterface $file * @param int $websiteId * @param string $conditionShortName @@ -132,7 +145,7 @@ public function getData(ReadInterface $file, $websiteId, $conditionShortName, $c if (empty($csvLine)) { continue; } - $rowData = $this->rowParser->parse( + $rowsData = $this->rowParser->parse( $csvLine, $rowNumber, $websiteId, @@ -141,20 +154,25 @@ public function getData(ReadInterface $file, $websiteId, $conditionShortName, $c $columnResolver ); - // protect from duplicate - $hash = $this->dataHashGenerator->getHash($rowData); - if (array_key_exists($hash, $this->uniqueHash)) { - throw new RowException( - __( - 'Duplicate Row #%1 (duplicates row #%2)', - $rowNumber, - $this->uniqueHash[$hash] - ) - ); + foreach ($rowsData as $rowData) { + // protect from duplicate + $hash = $this->dataHashGenerator->getHash($rowData); + if (array_key_exists($hash, $this->uniqueHash)) { + throw new RowException( + __( + 'Duplicate Row #%1 (duplicates row #%2)', + $rowNumber, + $this->uniqueHash[$hash] + ) + ); + } + $this->uniqueHash[$hash] = $rowNumber; + + $items[] = $rowData; + } + if (count($rowsData) > 1) { + $bunchSize += count($rowsData) - 1; } - $this->uniqueHash[$hash] = $rowNumber; - - $items[] = $rowData; if (count($items) === $bunchSize) { yield $items; $items = []; @@ -169,6 +187,8 @@ public function getData(ReadInterface $file, $websiteId, $conditionShortName, $c } /** + * Retrieve column headers. + * * @param ReadInterface $file * @return array|bool * @throws LocalizedException @@ -178,8 +198,11 @@ private function getHeaders(ReadInterface $file) // check and skip headers $headers = $file->readCsv(); if ($headers === false || count($headers) < 5) { - throw new LocalizedException(__('Please correct Table Rates File Format.')); + throw new LocalizedException( + __('The Table Rates File Format is incorrect. Verify the format and try again.') + ); } + return $headers; } } diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php index 1a311f3658a0a..3bccaaab481b3 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/LocationDirectory.php @@ -6,6 +6,9 @@ namespace Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate; +/** + * Location directory. + */ class LocationDirectory { /** @@ -13,6 +16,11 @@ class LocationDirectory */ protected $regions; + /** + * @var array + */ + private $regionsByCode; + /** * @var array */ @@ -47,6 +55,8 @@ public function __construct( } /** + * Retrieve country id. + * * @param string $countryCode * @return null|string */ @@ -64,7 +74,7 @@ public function getCountryId($countryCode) } /** - * Load directory countries + * Load directory countries. * * @return \Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate */ @@ -88,16 +98,21 @@ protected function loadCountries() } /** + * Check if there is country id with provided country code. + * * @param string $countryCode * @return bool */ public function hasCountryId($countryCode) { $this->loadCountries(); + return isset($this->iso2Countries[$countryCode]) || isset($this->iso3Countries[$countryCode]); } /** + * Check if there is region id with provided region code and country id. + * * @param string $countryId * @param string $regionCode * @return bool @@ -105,6 +120,7 @@ public function hasCountryId($countryCode) public function hasRegionId($countryId, $regionCode) { $this->loadRegions(); + return isset($this->regions[$countryId][$regionCode]); } @@ -115,29 +131,52 @@ public function hasRegionId($countryId, $regionCode) */ protected function loadRegions() { - if ($this->regions !== null) { + if ($this->regions !== null && $this->regionsByCode !== null) { return $this; } $this->regions = []; + $this->regionsByCode = []; /** @var $collection \Magento\Directory\Model\ResourceModel\Region\Collection */ $collection = $this->_regionCollectionFactory->create(); foreach ($collection->getData() as $row) { $this->regions[$row['country_id']][$row['code']] = (int)$row['region_id']; + if (empty($this->regionsByCode[$row['country_id']][$row['code']])) { + $this->regionsByCode[$row['country_id']][$row['code']] = []; + } + $this->regionsByCode[$row['country_id']][$row['code']][] = (int)$row['region_id']; } return $this; } /** + * Retrieve region id. + * * @param int $countryId * @param string $regionCode * @return string + * @deprecated */ public function getRegionId($countryId, $regionCode) { $this->loadRegions(); + return $this->regions[$countryId][$regionCode]; } + + /** + * Return region ids for country and region. + * + * @param string $countryId + * @param string $regionCode + * @return array + */ + public function getRegionIds(string $countryId, string $regionCode): array + { + $this->loadRegions(); + + return $this->regionsByCode[$countryId][$regionCode]; + } } diff --git a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php index 5b03ef0cb02bd..aa561bf4499c1 100644 --- a/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php +++ b/app/code/Magento/OfflineShipping/Model/ResourceModel/Carrier/Tablerate/RateQuery.php @@ -42,6 +42,7 @@ public function prepareSelect(\Magento\Framework\DB\Select $select) ') OR (', [ "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = :postcode", + "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = :postcode_prefix", "dest_country_id = :country_id AND dest_region_id = :region_id AND dest_zip = ''", // Handle asterisk in dest_zip field @@ -51,7 +52,7 @@ public function prepareSelect(\Magento\Framework\DB\Select $select) "dest_country_id = '0' AND dest_region_id = 0 AND dest_zip = '*'", "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = ''", "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = :postcode", - "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = '*'" + "dest_country_id = :country_id AND dest_region_id = 0 AND dest_zip = :postcode_prefix" ] ) . ')'; $select->where($orWhere); @@ -85,6 +86,7 @@ public function getBindings() ':country_id' => $this->request->getDestCountryId(), ':region_id' => (int)$this->request->getDestRegionId(), ':postcode' => $this->request->getDestPostcode(), + ':postcode_prefix' => $this->getDestPostcodePrefix() ]; // Render condition by condition name @@ -112,4 +114,18 @@ public function getRequest() { return $this->request; } + + /** + * Returns the entire postcode if it contains no dash + * or the part of it prior to the dash in the other case + * @return string + */ + private function getDestPostcodePrefix() + { + if (!preg_match("/^(.+)-(.+)$/", $this->request->getDestPostcode(), $zipParts)) { + return $this->request->getDestPostcode(); + } + + return $zipParts[1]; + } } diff --git a/app/code/Magento/OfflineShipping/Test/Mftf/composer.json b/app/code/Magento/OfflineShipping/Test/Mftf/composer.json deleted file mode 100644 index 8c61ffdc7257f..0000000000000 --- a/app/code/Magento/OfflineShipping/Test/Mftf/composer.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "magento/functional-test-module-offline-shipping", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-offline-shipping-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php index 661120c7b7c5f..683790c531265 100644 --- a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php +++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/CSV/RowParserTest.php @@ -37,7 +37,7 @@ class RowParserTest extends \PHPUnit\Framework\TestCase protected function setUp() { $this->locationDirectoryMock = $this->getMockBuilder(LocationDirectory::class) - ->setMethods(['hasCountryId', 'getCountryId', 'hasRegionId', 'getRegionId']) + ->setMethods(['hasCountryId', 'getCountryId', 'hasRegionId', 'getRegionIds']) ->disableOriginalConstructor() ->getMock(); $this->columnResolverMock = $this->getMockBuilder(ColumnResolver::class) @@ -92,7 +92,7 @@ public function testParse() $conditionShortName, $columnValueMap ); - $this->assertEquals($expectedResult, $result); + $this->assertEquals([$expectedResult], $result); } /** @@ -146,7 +146,7 @@ public function parseWithExceptionDataProvider() [$conditionFullName, $rowData, 40], [ColumnResolver::COLUMN_PRICE, $rowData, 350], ], - 'Please correct Country "XX" in the Row #120.', + 'The "XX" country in row number "120" is incorrect. Verify the country and try again.', ], [ $rowData, @@ -158,7 +158,7 @@ public function parseWithExceptionDataProvider() [$conditionFullName, $rowData, 40], [ColumnResolver::COLUMN_PRICE, $rowData, 350], ], - 'Please correct Region/State "AA" in the Row #120.', + 'The "AA" region or state in row number "120" is incorrect. Verify the region or state and try again.', ], [ $rowData, @@ -182,7 +182,7 @@ public function parseWithExceptionDataProvider() [$conditionFullName, $rowData, 40], [ColumnResolver::COLUMN_PRICE, $rowData, 'BBB'], ], - 'Please correct Shipping Price "BBB" in the Row #120.', + 'The "BBB" shipping price in row number "120" is incorrect. Verify the shipping price and try again.', ], ]; } diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php index 14fa8129532fa..722683decb4c2 100644 --- a/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php +++ b/app/code/Magento/OfflineShipping/Test/Unit/Model/ResourceModel/Carrier/Tablerate/ImportTest.php @@ -77,9 +77,6 @@ protected function setUp() ->getMock(); $this->dataHashGeneratorMock = $this->getMockBuilder(DataHashGenerator::class) ->getMock(); - $this->rowParserMock->expects($this->any()) - ->method('parse') - ->willReturnArgument(0); $this->dataHashGeneratorMock->expects($this->any()) ->method('getHash') ->willReturnCallback( @@ -124,6 +121,15 @@ public function testGetData() ['a4', 'b4', 'c4', 'd4', 'e4'], ['a5', 'b5', 'c5', 'd5', 'e5'], ]; + $this->rowParserMock->expects($this->any()) + ->method('parse') + ->willReturn( + [['a1', 'b1', 'c1', 'd1', 'e1']], + [['a2', 'b2', 'c2', 'd2', 'e2']], + [['a3', 'b3', 'c3', 'd3', 'e3']], + [['a4', 'b4', 'c4', 'd4', 'e4']], + [['a5', 'b5', 'c5', 'd5', 'e5']] + ); $file = $this->createFileMock($lines); $expectedResult = [ [ @@ -167,6 +173,13 @@ public function testGetDataWithDuplicatedLine() [], ['a2', 'b2', 'c2', 'd2', 'e2'], ]; + $this->rowParserMock->expects($this->any()) + ->method('parse') + ->willReturn( + [['a1', 'b1', 'c1', 'd1', 'e1']], + [['a1', 'b1', 'c1', 'd1', 'e1']], + [['a2', 'b2', 'c2', 'd2', 'e2']] + ); $file = $this->createFileMock($lines); $expectedResult = [ [ @@ -193,7 +206,7 @@ public function testGetDataWithDuplicatedLine() /** * @expectedException \Magento\Framework\Exception\LocalizedException - * @expectedExceptionMessage Please correct Table Rates File Format. + * @expectedExceptionMessage The Table Rates File Format is incorrect. Verify the format and try again. * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ public function testGetDataFromEmptyFile() diff --git a/app/code/Magento/OfflineShipping/composer.json b/app/code/Magento/OfflineShipping/composer.json index f364424736fde..24b5f877c1279 100644 --- a/app/code/Magento/OfflineShipping/composer.json +++ b/app/code/Magento/OfflineShipping/composer.json @@ -19,7 +19,7 @@ "magento/module-offline-shipping-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php b/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php new file mode 100644 index 0000000000000..e16584b0b17f8 --- /dev/null +++ b/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\PageCache\Model\System\Config\Backend; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + +/** + * Access List config field. + */ +class AccessList extends Varnish +{ + /** + * @inheritDoc + */ + public function beforeSave() + { + parent::beforeSave(); + + $value = $this->getValue(); + if (!is_string($value) || !preg_match('/^[\w\s\.\-\,\:]+$/', $value)) { + throw new LocalizedException( + new Phrase( + 'Access List value "%1" is not valid. ' + .'Please use only IP addresses and host names.', + [$value] + ) + ); + } + } +} diff --git a/app/code/Magento/PageCache/Test/Mftf/composer.json b/app/code/Magento/PageCache/Test/Mftf/composer.json deleted file mode 100644 index 1b2ce80091de9..0000000000000 --- a/app/code/Magento/PageCache/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-page-cache", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/PageCache/Test/Unit/Model/System/Config/Backend/AccessListTest.php b/app/code/Magento/PageCache/Test/Unit/Model/System/Config/Backend/AccessListTest.php new file mode 100644 index 0000000000000..1c4f279917c68 --- /dev/null +++ b/app/code/Magento/PageCache/Test/Unit/Model/System/Config/Backend/AccessListTest.php @@ -0,0 +1,90 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\PageCache\Test\Unit\Model\System\Config\Backend; + +use Magento\PageCache\Model\System\Config\Backend\AccessList; +use PHPUnit\Framework\TestCase; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\App\Config\ScopeConfigInterface; + +class AccessListTest extends TestCase +{ + /** + * @var AccessList + */ + private $accessList; + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManager = new ObjectManager($this); + $configMock = $this->getMockForAbstractClass( + ScopeConfigInterface::class + ); + $configMock->expects($this->any()) + ->method('getValue') + ->with('system/full_page_cache/default') + ->willReturn(['access_list' => 'localhost']); + $this->accessList = $objectManager->getObject( + AccessList::class, + [ + 'config' => $configMock, + 'data' => ['field' => 'access_list'] + ] + ); + } + + /** + * @return array + */ + public function getValidValues(): array + { + return [ + ['localhost', 'localhost'], + [null, 'localhost'], + ['127.0.0.1', '127.0.0.1'], + ['127.0.0.1, localhost, ::2', '127.0.0.1, localhost, ::2'], + ]; + } + + /** + * @param mixed $value + * @param mixed $expectedValue + * @dataProvider getValidValues + */ + public function testBeforeSave($value, $expectedValue) + { + $this->accessList->setValue($value); + $this->accessList->beforeSave(); + $this->assertEquals($expectedValue, $this->accessList->getValue()); + } + + /** + * @return array + */ + public function getInvalidValues(): array + { + return [ + ['\\bull val\\'], + ['{*I am not an IP*}'], + ['{*I am not an IP*}, 127.0.0.1'], + ]; + } + + /** + * @param mixed $value + * @expectedException \Magento\Framework\Exception\LocalizedException + * @dataProvider getInvalidValues + */ + public function testBeforeSaveInvalid($value) + { + $this->accessList->setValue($value); + $this->accessList->beforeSave(); + } +} diff --git a/app/code/Magento/PageCache/composer.json b/app/code/Magento/PageCache/composer.json index e2557a3e85cf9..d492f3bc23a5f 100644 --- a/app/code/Magento/PageCache/composer.json +++ b/app/code/Magento/PageCache/composer.json @@ -9,7 +9,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/PageCache/etc/adminhtml/system.xml b/app/code/Magento/PageCache/etc/adminhtml/system.xml index 5055b17956819..2a4439ac6a9cf 100644 --- a/app/code/Magento/PageCache/etc/adminhtml/system.xml +++ b/app/code/Magento/PageCache/etc/adminhtml/system.xml @@ -20,7 +20,7 @@ <label>Access list</label> <comment>IPs access list separated with ',' that can purge Varnish configuration for config file generation. If field is empty default value localhost will be saved.</comment> - <backend_model>Magento\PageCache\Model\System\Config\Backend\Varnish</backend_model> + <backend_model>Magento\PageCache\Model\System\Config\Backend\AccessList</backend_model> <depends> <field id="caching_application">1</field> </depends> diff --git a/app/code/Magento/Payment/Observer/SalesOrderBeforeSaveObserver.php b/app/code/Magento/Payment/Observer/SalesOrderBeforeSaveObserver.php index ed8185e6dedeb..3d520db833164 100644 --- a/app/code/Magento/Payment/Observer/SalesOrderBeforeSaveObserver.php +++ b/app/code/Magento/Payment/Observer/SalesOrderBeforeSaveObserver.php @@ -15,12 +15,19 @@ class SalesOrderBeforeSaveObserver implements ObserverInterface * * @param \Magento\Framework\Event\Observer $observer * @return $this + * @throws \Magento\Framework\Exception\LocalizedException in case order has no payment specified. */ public function execute(\Magento\Framework\Event\Observer $observer) { /** @var \Magento\Sales\Model\Order $order */ $order = $observer->getEvent()->getOrder(); + if (!$order->getPayment()) { + throw new \Magento\Framework\Exception\LocalizedException( + __('Please provide payment for the order.') + ); + } + if ($order->getPayment()->getMethodInstance()->getCode() != 'free') { return $this; } diff --git a/app/code/Magento/Payment/Test/Mftf/composer.json b/app/code/Magento/Payment/Test/Mftf/composer.json deleted file mode 100644 index 549139733a23e..0000000000000 --- a/app/code/Magento/Payment/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-payment", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Payment/Test/Unit/Observer/SalesOrderBeforeSaveObserverTest.php b/app/code/Magento/Payment/Test/Unit/Observer/SalesOrderBeforeSaveObserverTest.php index b86fbc6b18263..63ca1b47dc08c 100644 --- a/app/code/Magento/Payment/Test/Unit/Observer/SalesOrderBeforeSaveObserverTest.php +++ b/app/code/Magento/Payment/Test/Unit/Observer/SalesOrderBeforeSaveObserverTest.php @@ -61,7 +61,7 @@ public function testSalesOrderBeforeSaveCantUnhold() $paymentMock = $this->getMockBuilder( \Magento\Sales\Model\Order\Payment::class )->disableOriginalConstructor()->setMethods([])->getMock(); - $order->expects($this->once())->method('getPayment')->will($this->returnValue($paymentMock)); + $order->method('getPayment')->will($this->returnValue($paymentMock)); $methodInstance = $this->getMockBuilder( \Magento\Payment\Model\MethodInterface::class )->getMockForAbstractClass(); @@ -86,7 +86,7 @@ public function testSalesOrderBeforeSaveIsCanceled() $paymentMock = $this->getMockBuilder( \Magento\Sales\Model\Order\Payment::class )->disableOriginalConstructor()->setMethods([])->getMock(); - $order->expects($this->once())->method('getPayment')->will($this->returnValue($paymentMock)); + $order->method('getPayment')->will($this->returnValue($paymentMock)); $methodInstance = $this->getMockBuilder( \Magento\Payment\Model\MethodInterface::class )->getMockForAbstractClass(); @@ -114,7 +114,7 @@ public function testSalesOrderBeforeSaveIsClosed() $paymentMock = $this->getMockBuilder( \Magento\Sales\Model\Order\Payment::class )->disableOriginalConstructor()->setMethods([])->getMock(); - $order->expects($this->once())->method('getPayment')->will($this->returnValue($paymentMock)); + $order->method('getPayment')->will($this->returnValue($paymentMock)); $methodInstance = $this->getMockBuilder( \Magento\Payment\Model\MethodInterface::class )->getMockForAbstractClass(); @@ -156,6 +156,29 @@ public function testSalesOrderBeforeSaveSetForced() $this->salesOrderBeforeSaveObserver->execute($this->observerMock); } + /** + * The method should check that the payment is available, as this is not always the case. + * + * @expectedException \Magento\Framework\Exception\LocalizedException + * @exceptedExceptionMessage Please provide payment for the order. + */ + public function testDoesNothingWhenNoPaymentIsAvailable() + { + $this->_prepareEventMockWithMethods(['getOrder']); + + $order = $this->getMockBuilder(\Magento\Sales\Model\Order::class)->disableOriginalConstructor()->setMethods( + array_merge(['__wakeup', 'getPayment']) + )->getMock(); + + $this->eventMock->expects($this->once())->method('getOrder')->will( + $this->returnValue($order) + ); + + $order->expects($this->exactly(1))->method('getPayment')->willReturn(null); + + $this->salesOrderBeforeSaveObserver->execute($this->observerMock); + } + /** * Prepares EventMock with set of methods * @@ -184,7 +207,7 @@ private function _getPreparedOrderMethod($methodCode, $orderMethods = []) $paymentMock = $this->getMockBuilder( \Magento\Sales\Model\Order\Payment::class )->disableOriginalConstructor()->setMethods([])->getMock(); - $order->expects($this->once())->method('getPayment')->will($this->returnValue($paymentMock)); + $order->method('getPayment')->will($this->returnValue($paymentMock)); $methodInstance = $this->getMockBuilder( \Magento\Payment\Model\MethodInterface::class )->getMockForAbstractClass(); diff --git a/app/code/Magento/Payment/composer.json b/app/code/Magento/Payment/composer.json index bb0bbdac328ae..678aaa01c2ae8 100644 --- a/app/code/Magento/Payment/composer.json +++ b/app/code/Magento/Payment/composer.json @@ -12,7 +12,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Paypal/Model/Api/AbstractApi.php b/app/code/Magento/Paypal/Model/Api/AbstractApi.php index 89f83790263b8..98b052f51ace2 100644 --- a/app/code/Magento/Paypal/Model/Api/AbstractApi.php +++ b/app/code/Magento/Paypal/Model/Api/AbstractApi.php @@ -578,11 +578,11 @@ protected function _buildQuery($request) * Paypal note: The value for quantity must be a positive integer. Null, zero, or negative numbers are not allowed. * * @param float|string|int $value - * @return string + * @return int */ protected function _filterQty($value) { - return intval($value); + return (int)$value; } /** diff --git a/app/code/Magento/Paypal/Model/Api/PayflowNvp.php b/app/code/Magento/Paypal/Model/Api/PayflowNvp.php index 6373c6d99fb16..861d3ebcc9bc2 100644 --- a/app/code/Magento/Paypal/Model/Api/PayflowNvp.php +++ b/app/code/Magento/Paypal/Model/Api/PayflowNvp.php @@ -136,6 +136,9 @@ class PayflowNvp extends \Magento\Paypal\Model\Api\Nvp 'CVV2MATCH' => 'cvv2_check_result', 'USERSELECTEDFUNDINGSOURCE' => 'funding_source', + + 'NOSHIPPING' => 'suppress_shipping', + 'REQBILLINGADDRESS' => 'require_billing_address', ]; /** @@ -248,6 +251,8 @@ class PayflowNvp extends \Magento\Paypal\Model\Api\Nvp 'PAYFLOWCOLOR', 'LOCALECODE', 'USERSELECTEDFUNDINGSOURCE', + 'NOSHIPPING', + 'REQBILLINGADDRESS', ]; /** diff --git a/app/code/Magento/Paypal/Model/Express.php b/app/code/Magento/Paypal/Model/Express.php index 4684abdc9be6d..196e59c6593b9 100644 --- a/app/code/Magento/Paypal/Model/Express.php +++ b/app/code/Magento/Paypal/Model/Express.php @@ -432,8 +432,8 @@ public function void(\Magento\Payment\Model\InfoInterface $payment) public function capture(\Magento\Payment\Model\InfoInterface $payment, $amount) { $authorizationTransaction = $payment->getAuthorizationTransaction(); - $authorizationPeriod = abs(intval($this->getConfigData('authorization_honor_period'))); - $maxAuthorizationNumber = abs(intval($this->getConfigData('child_authorization_number'))); + $authorizationPeriod = abs((int)$this->getConfigData('authorization_honor_period')); + $maxAuthorizationNumber = abs((int)$this->getConfigData('child_authorization_number')); $order = $payment->getOrder(); $isAuthorizationCreated = false; @@ -750,7 +750,7 @@ public function canCapture() return false; } - $orderValidPeriod = abs(intval($this->getConfigData('order_valid_period'))); + $orderValidPeriod = abs((int)$this->getConfigData('order_valid_period')); $dateCompass = new \DateTime($orderTransaction->getCreatedAt()); $dateCompass->modify('+' . $orderValidPeriod . ' days'); @@ -805,7 +805,7 @@ protected function _callDoAuthorize($amount, $payment, $parentTransactionId) */ protected function _isTransactionExpired(Transaction $transaction, $period) { - $period = intval($period); + $period = (int)$period; if (0 == $period) { return true; } diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php index 2e09d2bb6698b..f0b86588f1cfa 100644 --- a/app/code/Magento/Paypal/Model/Express/Checkout.php +++ b/app/code/Magento/Paypal/Model/Express/Checkout.php @@ -14,8 +14,8 @@ use Magento\Sales\Model\Order\Email\Sender\OrderSender; /** - * Wrapper that performs Paypal Express and Checkout communication - * Use current Paypal Express method instance + * Wrapper that performs Paypal Express and Checkout communication. + * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -24,6 +24,7 @@ class Checkout { /** * Cache ID prefix for "pal" lookup + * * @var string */ const PAL_CACHE_ID = 'paypal_express_checkout_pal'; @@ -364,8 +365,9 @@ public function __construct( } /** - * Checkout with PayPal image URL getter - * Spares API calls of getting "pal" variable, by putting it into cache per store view + * Checkout with PayPal image URL getter. + * + * Spares API calls of getting "pal" variable, by putting it into cache per store view. * * @return string */ @@ -597,8 +599,8 @@ public function canSkipOrderReviewStep() /** * Update quote when returned from PayPal - * rewrite billing address by paypal - * save old billing address for new customer + * + * Rewrite billing address by paypal, save old billing address for new customer, and * export shipping address in case address absence * * @param string $token @@ -614,14 +616,15 @@ public function returnFromPaypal($token) $this->ignoreAddressValidation(); + // check if we came from the Express Checkout button + $isButton = (bool)$quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON); + // import shipping address $exportedShippingAddress = $this->_getApi()->getExportedShippingAddress(); if (!$quote->getIsVirtual()) { $shippingAddress = $quote->getShippingAddress(); if ($shippingAddress) { - if ($exportedShippingAddress - && $quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON) == 1 - ) { + if ($exportedShippingAddress && $isButton) { $this->_setExportedAddressData($shippingAddress, $exportedShippingAddress); // PayPal doesn't provide detailed shipping info: prefix, middlename, lastname, suffix $shippingAddress->setPrefix(null); @@ -649,12 +652,11 @@ public function returnFromPaypal($token) } // import billing address - $portBillingFromShipping = $quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON) == 1 - && $this->_config->getValue( - 'requireBillingAddress' - ) != \Magento\Paypal\Model\Config::REQUIRE_BILLING_ADDRESS_ALL - && !$quote->isVirtual(); - if ($portBillingFromShipping) { + $requireBillingAddress = (int)$this->_config->getValue( + 'requireBillingAddress' + ) === \Magento\Paypal\Model\Config::REQUIRE_BILLING_ADDRESS_ALL; + + if ($isButton && !$requireBillingAddress && !$quote->isVirtual()) { $billingAddress = clone $shippingAddress; $billingAddress->unsAddressId()->unsAddressType()->setCustomerAddressId(null); $data = $billingAddress->getData(); @@ -662,11 +664,17 @@ public function returnFromPaypal($token) $quote->getBillingAddress()->addData($data); $quote->getShippingAddress()->setSameAsBilling(1); } else { - $billingAddress = $quote->getBillingAddress(); + $billingAddress = $quote->getBillingAddress()->setCustomerAddressId(null); } $exportedBillingAddress = $this->_getApi()->getExportedBillingAddress(); - $this->_setExportedAddressData($billingAddress, $exportedBillingAddress); + // Since country is required field for billing and shipping address, + // we consider the address information to be empty if country is empty. + $isEmptyAddress = ($billingAddress->getCountryId() === null); + + if ($requireBillingAddress || $isEmptyAddress) { + $this->_setExportedAddressData($billingAddress, $exportedBillingAddress); + } $billingAddress->setCustomerNote($exportedBillingAddress->getData('note')); $quote->setBillingAddress($billingAddress); $quote->setCheckoutMethod($this->getCheckoutMethod()); @@ -894,7 +902,7 @@ public function getCheckoutMethod() } /** - * Sets address data from exported address + * Sets address data from exported address. * * @param Address $address * @param array $exportedAddress @@ -902,17 +910,6 @@ public function getCheckoutMethod() */ protected function _setExportedAddressData($address, $exportedAddress) { - // Exported data is more priority if we came from Express Checkout button - $isButton = (bool)$this->_quote->getPayment()->getAdditionalInformation(self::PAYMENT_INFO_BUTTON); - - // Since country is required field for billing and shipping address, - // we consider the address information to be empty if country is empty. - $isEmptyAddress = ($address->getCountryId() === null); - - if (!$isButton && !$isEmptyAddress) { - return; - } - foreach ($exportedAddress->getExportedKeys() as $key) { $data = $exportedAddress->getData($key); if (!empty($data)) { @@ -949,9 +946,11 @@ protected function _setBillingAgreementRequest() } /** + * Get api + * * @return \Magento\Paypal\Model\Api\Nvp */ - protected function _getApi() + protected function _getApi(): \Magento\Paypal\Model\Api\Nvp { if (null === $this->_api) { $this->_api = $this->_apiTypeFactory->create($this->_apiType)->setConfigObject($this->_config); @@ -960,9 +959,10 @@ protected function _getApi() } /** - * Attempt to collect address shipping rates and return them for further usage in instant update API - * Returns empty array if it was impossible to obtain any shipping rate - * If there are shipping rates obtained, the method must return one of them as default. + * Attempt to collect address shipping rates and return them for further usage in instant update API. + * + * Returns empty array if it was impossible to obtain any shipping rate and + * if there are shipping rates obtained, the method must return one of them as default. * * @param Address $address * @param bool $mayReturnEmpty @@ -1043,25 +1043,23 @@ protected function _prepareShippingOptions(Address $address, $mayReturnEmpty = f } /** - * Compare two shipping options based on their amounts + * Compare two shipping options based on their amounts. * - * This function is used as a callback comparison function in shipping options sorting process - * @see self::_prepareShippingOptions() + * This function is used as a callback comparison function in shipping options sorting process. * + * @see self::_prepareShippingOptions() * @param \Magento\Framework\DataObject $option1 * @param \Magento\Framework\DataObject $option2 * @return int */ - protected static function cmpShippingOptions(DataObject $option1, DataObject $option2) + protected static function cmpShippingOptions(DataObject $option1, DataObject $option2): int { - if ($option1->getAmount() == $option2->getAmount()) { - return 0; - } - return ($option1->getAmount() < $option2->getAmount()) ? -1 : 1; + return $option1->getAmount() <=> $option2->getAmount(); } /** - * Try to find whether the code provided by PayPal corresponds to any of possible shipping rates + * Try to find whether the code provided by PayPal corresponds to any of possible shipping rates. + * * This method was created only because PayPal has issues with returning the selected code. * If in future the issue is fixed, we don't need to attempt to match it. It would be enough to set the method code * before collecting shipping rates @@ -1070,7 +1068,7 @@ protected static function cmpShippingOptions(DataObject $option1, DataObject $op * @param string $selectedCode * @return string */ - protected function _matchShippingMethodCode(Address $address, $selectedCode) + protected function _matchShippingMethodCode(Address $address, $selectedCode): string { $options = $this->_prepareShippingOptions($address, false); foreach ($options as $option) { @@ -1086,7 +1084,8 @@ protected function _matchShippingMethodCode(Address $address, $selectedCode) } /** - * Create payment redirect url + * Create payment redirect url. + * * @param bool|null $button * @param string $token * @return void @@ -1109,7 +1108,8 @@ public function getCustomerSession() } /** - * Set shipping options to api + * Set shipping options to api. + * * @param \Magento\Paypal\Model\Cart $cart * @param \Magento\Quote\Model\Quote\Address|null $address * @return void diff --git a/app/code/Magento/Paypal/Model/Report/Settlement.php b/app/code/Magento/Paypal/Model/Report/Settlement.php index 96089e83a8aab..d7490056a77d1 100644 --- a/app/code/Magento/Paypal/Model/Report/Settlement.php +++ b/app/code/Magento/Paypal/Model/Report/Settlement.php @@ -454,7 +454,7 @@ private function formatDateTimeColumns($lineItem) */ private function formatAmountColumn($lineItem) { - return intval($lineItem) / 100; + return (int)$lineItem / 100; } /** diff --git a/app/code/Magento/Paypal/Plugin/OrderCanInvoice.php b/app/code/Magento/Paypal/Plugin/OrderCanInvoice.php index 8393b756be364..434082728513d 100644 --- a/app/code/Magento/Paypal/Plugin/OrderCanInvoice.php +++ b/app/code/Magento/Paypal/Plugin/OrderCanInvoice.php @@ -38,6 +38,10 @@ public function __construct(Express $express) */ public function afterCanInvoice(Order $order, $result): bool { + if (!$order->getPayment()) { + return false; + } + if ($this->express->isOrderAuthorizationAllowed($order->getPayment())) { return false; } diff --git a/app/code/Magento/Paypal/Test/Mftf/composer.json b/app/code/Magento/Paypal/Test/Mftf/composer.json deleted file mode 100644 index 60d8caccafe0f..0000000000000 --- a/app/code/Magento/Paypal/Test/Mftf/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "magento/functional-test-module-paypal", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-vault": "100.0.0-dev", - "magento/functional-test-module-instant-purchase": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-checkout-agreements": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Paypal/composer.json b/app/code/Magento/Paypal/composer.json index fb9b2f08f730c..281b7084e7f1b 100644 --- a/app/code/Magento/Paypal/composer.json +++ b/app/code/Magento/Paypal/composer.json @@ -26,7 +26,7 @@ "magento/module-checkout-agreements": "100.2.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "proprietary" ], diff --git a/app/code/Magento/Persistent/Helper/Data.php b/app/code/Magento/Persistent/Helper/Data.php index 39a9ce7a8ef43..c963084a1ecb1 100644 --- a/app/code/Magento/Persistent/Helper/Data.php +++ b/app/code/Magento/Persistent/Helper/Data.php @@ -136,12 +136,10 @@ public function isShoppingCartPersist($store = null) */ public function getLifeTime($store = null) { - $lifeTime = intval( - $this->scopeConfig->getValue( - self::XML_PATH_LIFE_TIME, - ScopeInterface::SCOPE_STORE, - $store - ) + $lifeTime = (int)$this->scopeConfig->getValue( + self::XML_PATH_LIFE_TIME, + ScopeInterface::SCOPE_STORE, + $store ); return $lifeTime < 0 ? 0 : $lifeTime; } diff --git a/app/code/Magento/Persistent/Model/Session.php b/app/code/Magento/Persistent/Model/Session.php index abc344671ab70..3aea20a090dd9 100644 --- a/app/code/Magento/Persistent/Model/Session.php +++ b/app/code/Magento/Persistent/Model/Session.php @@ -354,7 +354,7 @@ public function deleteExpired($websiteId = null) $lifetime = $this->_coreConfig->getValue( \Magento\Persistent\Helper\Data::XML_PATH_LIFE_TIME, 'website', - intval($websiteId) + (int)$websiteId ); if ($lifetime) { diff --git a/app/code/Magento/Persistent/Test/Mftf/composer.json b/app/code/Magento/Persistent/Test/Mftf/composer.json deleted file mode 100644 index dffd7e23ae934..0000000000000 --- a/app/code/Magento/Persistent/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-persistent", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-cron": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Persistent/composer.json b/app/code/Magento/Persistent/composer.json index 7077d13b5e21d..9debadd193a9d 100644 --- a/app/code/Magento/Persistent/composer.json +++ b/app/code/Magento/Persistent/composer.json @@ -12,7 +12,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/ProductAlert/Test/Mftf/composer.json b/app/code/Magento/ProductAlert/Test/Mftf/composer.json deleted file mode 100644 index cb057b6554453..0000000000000 --- a/app/code/Magento/ProductAlert/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-product-alert", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ProductAlert/composer.json b/app/code/Magento/ProductAlert/composer.json index fb74623ce4550..11dc94edcbcd3 100644 --- a/app/code/Magento/ProductAlert/composer.json +++ b/app/code/Magento/ProductAlert/composer.json @@ -13,7 +13,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/ProductVideo/Block/Product/View/Gallery.php b/app/code/Magento/ProductVideo/Block/Product/View/Gallery.php index 0278ec8dadf9f..d6a1929db4d13 100644 --- a/app/code/Magento/ProductVideo/Block/Product/View/Gallery.php +++ b/app/code/Magento/ProductVideo/Block/Product/View/Gallery.php @@ -9,6 +9,7 @@ * * @author Magento Core Team <core@magentocommerce.com> */ + namespace Magento\ProductVideo\Block\Product\View; /** @@ -85,6 +86,6 @@ public function getVideoSettingsJson() */ public function getOptionsMediaGalleryDataJson() { - return $this->jsonEncoder->encode([]); + return $this->jsonEncoder->encode([]); } } diff --git a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php index c06ce8355d024..c56e5e3139517 100644 --- a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php +++ b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/CreateHandler.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ProductVideo\Model\Plugin\Catalog\Product\Gallery; use Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter; @@ -30,6 +31,7 @@ public function beforeExecute( \Magento\Catalog\Model\Product $product, array $arguments = [] ) { + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ $attribute = $mediaGalleryCreateHandler->getAttribute(); $mediaCollection = $this->getMediaEntriesDataCollection($product, $attribute); if (!empty($mediaCollection)) { @@ -37,7 +39,7 @@ public function beforeExecute( $mediaCollection = $this->addAdditionalStoreData($mediaCollection, $storeDataCollection); $product->setData( $attribute->getAttributeCode(), - $mediaCollection + $product->getData($attribute->getAttributeCode()) + $mediaCollection ); } } @@ -57,6 +59,9 @@ public function afterExecute( ); if (!empty($mediaCollection)) { + $newVideoCollection = $this->collectNewVideos($mediaCollection); + $this->saveVideoData($newVideoCollection, 0); + $videoDataCollection = $this->collectVideoData($mediaCollection); $this->saveVideoData($videoDataCollection, $product->getStoreId()); $this->saveAdditionalStoreData($videoDataCollection); @@ -168,10 +173,7 @@ protected function collectVideoData(array $mediaCollection) { $videoDataCollection = []; foreach ($mediaCollection as $item) { - if (!empty($item['media_type']) - && empty($item['removed']) - && $item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE - ) { + if ($this->isVideoItem($item)) { $videoData = $this->extractVideoDataFromRowData($item); $videoDataCollection[] = $videoData; } @@ -200,11 +202,7 @@ protected function collectVideoEntriesIdsToAdditionalLoad(array $mediaCollection { $ids = []; foreach ($mediaCollection as $item) { - if (!empty($item['media_type']) - && empty($item['removed']) - && $item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE - && isset($item['save_data_from']) - ) { + if ($this->isVideoItem($item) && isset($item['save_data_from'])) { $ids[] = $item['save_data_from']; } } @@ -216,18 +214,19 @@ protected function collectVideoEntriesIdsToAdditionalLoad(array $mediaCollection * @param array $data * @return array */ - protected function addAdditionalStoreData(array $mediaCollection, array $data) + protected function addAdditionalStoreData(array $mediaCollection, array $data): array { - foreach ($mediaCollection as &$mediaItem) { + $return = []; + foreach ($mediaCollection as $key => $mediaItem) { if (!empty($mediaItem['save_data_from'])) { $additionalData = $this->createAdditionalStoreDataCollection($data, $mediaItem['save_data_from']); if (!empty($additionalData)) { $mediaItem[self::ADDITIONAL_STORE_DATA_KEY] = $additionalData; } } + $return[$key] = $mediaItem; } - - return ['images' => $mediaCollection]; + return ['images' => $return]; } /** @@ -235,7 +234,7 @@ protected function addAdditionalStoreData(array $mediaCollection, array $data) * @param int $valueId * @return array */ - protected function createAdditionalStoreDataCollection(array $storeData, $valueId) + protected function createAdditionalStoreDataCollection(array $storeData, $valueId): array { $result = []; foreach ($storeData as $item) { @@ -247,4 +246,41 @@ protected function createAdditionalStoreDataCollection(array $storeData, $valueI return $result; } + + /** + * @param array $mediaCollection + * @return array + */ + private function collectNewVideos(array $mediaCollection): array + { + $return = []; + foreach ($mediaCollection as $item) { + if ($this->isVideoItem($item) && $this->isNewVideo($item)) { + $return[] = $this->extractVideoDataFromRowData($item); + } + } + return $return; + } + + /** + * @param $item + * @return bool + */ + private function isVideoItem($item): bool + { + return !empty($item['media_type']) + && empty($item['removed']) + && $item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE; + } + + /** + * @param $item + * @return bool + */ + private function isNewVideo($item): bool + { + return !isset($item['video_url_default'], $item['video_title_default']) + || empty($item['video_url_default']) + || empty($item['video_title_default']); + } } diff --git a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/ReadHandler.php b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/ReadHandler.php index a6225fa40c1b3..cd785a583b6b5 100644 --- a/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/ReadHandler.php +++ b/app/code/Magento/ProductVideo/Model/Plugin/Catalog/Product/Gallery/ReadHandler.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ProductVideo\Model\Plugin\Catalog\Product\Gallery; use Magento\ProductVideo\Model\Product\Attribute\Media\ExternalVideoEntryConverter; @@ -56,8 +57,8 @@ protected function collectVideoEntriesIds(array $mediaCollection) { $ids = []; foreach ($mediaCollection as $item) { - if ($item['media_type'] == ExternalVideoEntryConverter::MEDIA_TYPE_CODE - && !array_key_exists('video_url', $item) + if ($item['media_type'] === ExternalVideoEntryConverter::MEDIA_TYPE_CODE + && !isset($item['video_url']) ) { $ids[] = $item['value_id']; } @@ -73,7 +74,7 @@ protected function collectVideoEntriesIds(array $mediaCollection) protected function loadVideoDataById(array $ids, $storeId = null) { $mainTableAlias = $this->resourceModel->getMainTableAlias(); - $joinConditions = $mainTableAlias.'.value_id = store_value.value_id'; + $joinConditions = $mainTableAlias . '.value_id = store_value.value_id'; if (null !== $storeId) { $joinConditions = implode( ' AND ', @@ -135,10 +136,10 @@ protected function addVideoDataToMediaEntries(array $mediaCollection, array $dat protected function substituteNullsWithDefaultValues(array $rowData) { foreach ($this->getVideoProperties(false) as $key) { - if (empty($rowData[$key]) && !empty($rowData[$key.'_default'])) { - $rowData[$key] = $rowData[$key.'_default']; + if (empty($rowData[$key]) && !empty($rowData[$key . '_default'])) { + $rowData[$key] = $rowData[$key . '_default']; } - unset($rowData[$key.'_default']); + unset($rowData[$key . '_default']); } return $rowData; @@ -151,8 +152,7 @@ protected function substituteNullsWithDefaultValues(array $rowData) protected function getVideoProperties($withDbMapping = true) { $properties = $this->videoPropertiesDbMapping; - unset($properties['value_id']); - unset($properties['store_id']); + unset($properties['value_id'], $properties['store_id']); return $withDbMapping ? $properties : array_keys($properties); } diff --git a/app/code/Magento/ProductVideo/Test/Mftf/composer.json b/app/code/Magento/ProductVideo/Test/Mftf/composer.json deleted file mode 100644 index 0ea2d018a602a..0000000000000 --- a/app/code/Magento/ProductVideo/Test/Mftf/composer.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "magento/functional-test-module-product-video", - "description": "Add Video to Products", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/Catalog/Product/Gallery/CreateHandlerTest.php b/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/Catalog/Product/Gallery/CreateHandlerTest.php index 5770ea8b5689d..f6a72ee94b577 100644 --- a/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/Catalog/Product/Gallery/CreateHandlerTest.php +++ b/app/code/Magento/ProductVideo/Test/Unit/Model/Plugin/Catalog/Product/Gallery/CreateHandlerTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\ProductVideo\Test\Unit\Model\Plugin\Catalog\Product\Gallery; /** @@ -37,6 +38,9 @@ class CreateHandlerTest extends \PHPUnit\Framework\TestCase */ protected $mediaGalleryCreateHandler; + /** + * {@inheritDoc} + */ protected function setUp() { $this->product = $this->createMock(\Magento\Catalog\Model\Product::class); @@ -62,72 +66,18 @@ protected function setUp() ); } - public function testAfterExecute() + /** + * @dataProvider provideImageForAfterExecute + * @param array $image + * @param array $expectedSave + * @param int $rowSaved + */ + public function testAfterExecute($image, $expectedSave, $rowSaved) { - $mediaData = [ - 'images' => [ - '72mljfhmasfilp9cuq' => [ - 'position' => '3', - 'media_type' => 'external-video', - 'file' => '/i/n/index111111.jpg', - 'value_id' => '4', - 'label' => '', - 'disabled' => '0', - 'removed' => '', - 'video_provider' => 'youtube', - 'video_url' => 'https://www.youtube.com/watch?v=ab123456', - 'video_title' => 'Some second title', - 'video_description' => 'Description second', - 'video_metadata' => 'meta two', - 'role' => '', - ], - 'w596fi79hv1p6wj21u' => [ - 'position' => '4', - 'media_type' => 'image', - 'video_provider' => '', - 'file' => '/h/d/hd_image.jpg', - 'value_id' => '7', - 'label' => '', - 'disabled' => '0', - 'removed' => '', - 'video_url' => '', - 'video_title' => '', - 'video_description' => '', - 'video_metadata' => '', - 'role' => '', - ], - 'tcodwd7e0dirifr64j' => [ - 'position' => '4', - 'media_type' => 'external-video', - 'file' => '/s/a/sample_3.jpg', - 'value_id' => '5', - 'label' => '', - 'disabled' => '0', - 'removed' => '', - 'video_provider' => 'youtube', - 'video_url' => 'https://www.youtube.com/watch?v=ab123456', - 'video_title' => 'Some second title', - 'video_description' => 'Description second', - 'video_metadata' => 'meta two', - 'role' => '', - 'additional_store_data' => [ - 0 => [ - 'store_id' => '0', - 'video_provider' => null, - 'video_url' => 'https://www.youtube.com/watch?v=ab123456', - 'video_title' => 'New Title', - 'video_description' => 'New Description', - 'video_metadata' => 'New metadata', - ], - ] - ], - ], - ]; - $this->product->expects($this->once()) ->method('getData') ->with('media_gallery') - ->willReturn($mediaData); + ->willReturn(['images' => $image]); $this->product->expects($this->once()) ->method('getStoreId') ->willReturn(0); @@ -136,12 +86,149 @@ public function testAfterExecute() ->method('getAttribute') ->willReturn($this->attribute); - $this->subject->afterExecute( - $this->mediaGalleryCreateHandler, - $this->product - ); + $this->resourceModel->expects($this->exactly($rowSaved)) + ->method('saveDataRow') + ->with('catalog_product_entity_media_gallery_value_video', $expectedSave) + ->willReturn(1); + + $this->subject->afterExecute($this->mediaGalleryCreateHandler, $this->product); + } + + /** + * DataProvider for testAfterExecute + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return array + */ + public function provideImageForAfterExecute(): array + { + return [ + 'new_video' => [ + [ + '72mljfhmasfilp9cuq' => [ + 'position' => '3', + 'media_type' => 'external-video', + 'file' => '/i/n/index111111.jpg', + 'value_id' => '4', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + ], + ], + [ + 'value_id' => '4', + 'store_id' => 0, + 'provider' => 'youtube', + 'url' => 'https://www.youtube.com/watch?v=ab123456', + 'title' => 'Some second title', + 'description' => 'Description second', + 'metadata' => 'meta two', + ], + 2 + ], + 'image' => [ + [ + 'w596fi79hv1p6wj21u' => [ + 'position' => '4', + 'media_type' => 'image', + 'video_provider' => '', + 'file' => '/h/d/hd_image.jpg', + 'value_id' => '7', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_url' => '', + 'video_title' => '', + 'video_description' => '', + 'video_metadata' => '', + 'role' => '', + ], + ], + [], + 0 + ], + 'new_video_with_additional_data' => [ + [ + 'tcodwd7e0dirifr64j' => [ + 'position' => '4', + 'media_type' => 'external-video', + 'file' => '/s/a/sample_3.jpg', + 'value_id' => '5', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + 'additional_store_data' => [ + 0 => [ + 'store_id' => 0, + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + ], + ] + ], + ], + [ + 'value_id' => '5', + 'store_id' => 0, + 'provider' => 'youtube', + 'url' => 'https://www.youtube.com/watch?v=ab123456', + 'title' => 'Some second title', + 'description' => 'Description second', + 'metadata' => 'meta two', + ], + 3 + ], + 'not_new_video' => [ + [ + '72mljfhmasfilp9cuq' => [ + 'position' => '3', + 'media_type' => 'external-video', + 'file' => '/i/n/index111111.jpg', + 'value_id' => '4', + 'label' => '', + 'disabled' => '0', + 'removed' => '', + 'video_provider' => 'youtube', + 'video_url' => 'https://www.youtube.com/watch?v=ab123456', + 'video_url_default' => 'https://www.youtube.com/watch?v=ab123456', + 'video_title' => 'Some second title', + 'video_title_default' => 'Some second title', + 'video_description' => 'Description second', + 'video_metadata' => 'meta two', + 'role' => '', + ], + ], + [ + 'value_id' => '4', + 'store_id' => 0, + 'provider' => 'youtube', + 'url' => 'https://www.youtube.com/watch?v=ab123456', + 'title' => 'Some second title', + 'description' => 'Description second', + 'metadata' => 'meta two', + ], + 1 + ], + ]; } + /** + * Tests empty media gallery + */ public function testAfterExecuteEmpty() { $this->product->expects($this->once()) diff --git a/app/code/Magento/ProductVideo/composer.json b/app/code/Magento/ProductVideo/composer.json index 71a73c9d19a6a..811422d160d8d 100644 --- a/app/code/Magento/ProductVideo/composer.json +++ b/app/code/Magento/ProductVideo/composer.json @@ -16,7 +16,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "proprietary" ], diff --git a/app/code/Magento/ProductVideo/i18n/de_DE.csv b/app/code/Magento/ProductVideo/i18n/de_DE.csv index 7047317396999..ca24668bb8d16 100644 --- a/app/code/Magento/ProductVideo/i18n/de_DE.csv +++ b/app/code/Magento/ProductVideo/i18n/de_DE.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/en_US.csv b/app/code/Magento/ProductVideo/i18n/en_US.csv index 2d226c6daefa3..debcab151cc91 100644 --- a/app/code/Magento/ProductVideo/i18n/en_US.csv +++ b/app/code/Magento/ProductVideo/i18n/en_US.csv @@ -40,3 +40,4 @@ Delete,Delete "Autostart base video","Autostart base video" "Show related video","Show related video" "Auto restart video","Auto restart video" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/es_ES.csv b/app/code/Magento/ProductVideo/i18n/es_ES.csv index 7047317396999..ca24668bb8d16 100644 --- a/app/code/Magento/ProductVideo/i18n/es_ES.csv +++ b/app/code/Magento/ProductVideo/i18n/es_ES.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/fr_FR.csv b/app/code/Magento/ProductVideo/i18n/fr_FR.csv index 7047317396999..ca24668bb8d16 100644 --- a/app/code/Magento/ProductVideo/i18n/fr_FR.csv +++ b/app/code/Magento/ProductVideo/i18n/fr_FR.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/nl_NL.csv b/app/code/Magento/ProductVideo/i18n/nl_NL.csv index 7047317396999..ca24668bb8d16 100644 --- a/app/code/Magento/ProductVideo/i18n/nl_NL.csv +++ b/app/code/Magento/ProductVideo/i18n/nl_NL.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/pt_BR.csv b/app/code/Magento/ProductVideo/i18n/pt_BR.csv index 7047317396999..ca24668bb8d16 100644 --- a/app/code/Magento/ProductVideo/i18n/pt_BR.csv +++ b/app/code/Magento/ProductVideo/i18n/pt_BR.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv index 7047317396999..ca24668bb8d16 100644 --- a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv +++ b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv @@ -7,3 +7,4 @@ "Preview Image","Preview Image" "Get Video Information","Get Video Information" "Youtube or Vimeo supported","Youtube or Vimeo supported" +"Delete image in all store views","Delete image in all store views" diff --git a/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml b/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml index f5a22c50e6d0d..63bd5321ad30b 100644 --- a/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml +++ b/app/code/Magento/ProductVideo/view/adminhtml/layout/catalog_product_new.xml @@ -6,6 +6,9 @@ */ --> <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> + <head> + <css src="Magento_ProductVideo::css/gallery-delete-tooltip.css"/> + </head> <body> <referenceContainer name="content"> diff --git a/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml b/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml index f0ae057bc724d..2ceb9e9c9d4f2 100755 --- a/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml +++ b/app/code/Magento/ProductVideo/view/adminhtml/templates/helper/gallery.phtml @@ -34,7 +34,7 @@ $elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'to class="gallery" data-mage-init='{"openVideoModal":{}}' data-parent-component="<?= $block->escapeHtml($block->getData('config/parentComponent')) ?>" - data-images="<?= $block->escapeHtml($block->getImagesJson()) ?>" + data-images="<?= $block->escapeHtmlAttr($block->getImagesJson()) ?>" data-types="<?= $block->escapeHtml( $this->helper('Magento\Framework\Json\Helper\Data')->jsonEncode($block->getImageTypes()) ) ?>" @@ -140,30 +140,37 @@ $elementToggleCode = $element->getToggleCode() ? $element->getToggleCode() : 'to alt="<%- data.label %>"/> <div class="actions"> - <button type="button" - class="action-remove" - data-role="delete-button" - title="<% if (data.media_type == 'external-video') {%> + <div class="tooltip"> + <span class="delete-tooltiptext"> + <?= $block->escapeHtml( + __('Delete image in all store views') + ); ?> + </span> + <button type="button" + class="action-remove" + data-role="delete-button" + title="<% if (data.media_type == 'external-video') {%> + <?= $block->escapeHtml( + __('Delete video') + ); ?> + <%} else {%> + <?= $block->escapeHtml( + __('Delete image') + ); ?> + <%}%>"> + <span> + <% if (data.media_type == 'external-video') { %> <?= $block->escapeHtml( __('Delete video') ); ?> - <%} else {%> + <% } else {%> <?= $block->escapeHtml( __('Delete image') ); ?> - <%}%>"> - <span> - <% if (data.media_type == 'external-video') { %> - <?= $block->escapeHtml( - __('Delete video') - ); ?> - <% } else {%> - <?= $block->escapeHtml( - __('Delete image') - ); ?> - <%} %> - </span> - </button> + <%} %> + </span> + </button> + </div> <div class="draggable-handle"></div> </div> <div class="image-fade"><span><?= $block->escapeHtml( diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css b/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css new file mode 100644 index 0000000000000..835a22683b157 --- /dev/null +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/css/gallery-delete-tooltip.css @@ -0,0 +1,20 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +.gallery .tooltip .delete-tooltiptext { + visibility: hidden; + width: 112px; + background-color: #373330; + color: #F7F3EB; + text-align: center; + padding: 5px 0; + position: absolute; + z-index: 1; + left: 30px; + top: 91px; +} + +.gallery .tooltip:hover .delete-tooltiptext { + visibility: visible; +} diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js b/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js index 13b0e43a84d81..653434f1008ca 100644 --- a/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/js/get-video-information.js @@ -86,6 +86,7 @@ define([ this._height = this.element.data('height'); this._autoplay = !!this.element.data('autoplay'); this._playing = this._autoplay || false; + this.useYoutubeNocookie = this.element.data('youtubenocookie') || false; this._responsive = this.element.data('responsive') !== false; @@ -163,6 +164,12 @@ define([ * @private */ 'youtubeapiready': function () { + var host = 'https://www.youtube.com'; + + if (self.useYoutubeNocookie) { + host = 'https://www.youtube-nocookie.com'; + } + if (self._player !== undefined) { return; } @@ -177,6 +184,7 @@ define([ width: self._width, videoId: self._code, playerVars: self._params, + host: host, events: { /** @@ -469,7 +477,8 @@ define([ description: tmp.snippet.description, thumbnail: tmp.snippet.thumbnails.high.url, videoId: videoInfo.id, - videoProvider: videoInfo.type + videoProvider: videoInfo.type, + useYoutubeNocookie: videoInfo.useYoutubeNocookie }; this._videoInformation = respData; this.element.trigger(this._UPDATE_VIDEO_INFORMATION_TRIGGER, respData); @@ -600,7 +609,8 @@ define([ var id, type, ampersandPosition, - vimeoRegex; + vimeoRegex, + useYoutubeNocookie = false; if (typeof href !== 'string') { return href; @@ -620,9 +630,13 @@ define([ id = id.substring(0, ampersandPosition); } - } else if (href.host.match(/youtube\.com|youtu\.be/)) { + } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) { id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, ''); type = 'youtube'; + + if (href.host.match(/youtube-nocookie.com/)) { + useYoutubeNocookie = true; + } } else if (href.host.match(/vimeo\.com/)) { type = 'vimeo'; vimeoRegex = new RegExp(['https?:\\/\\/(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)', @@ -640,7 +654,7 @@ define([ } return id ? { - id: id, type: type, s: href.search.replace(/^\?/, '') + id: id, type: type, s: href.search.replace(/^\?/, ''), useYoutubeNocookie: useYoutubeNocookie } : false; } }); diff --git a/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js b/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js index 1ab10c95a51bc..e9b234c5f1160 100644 --- a/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js +++ b/app/code/Magento/ProductVideo/view/adminhtml/web/js/new-video-dialog.js @@ -21,6 +21,7 @@ define([ container: '.video-player-container', videoClass: 'product-video', reset: false, + useYoutubeNocookie: false, metaData: { DOM: { title: '.video-information.title span', @@ -86,34 +87,36 @@ define([ * @private */ _doUpdate: function () { + var uploaderLinkUrl, + uploaderLink; + this.reset(); - this.element.find(this.options.container).append('<div class="' + - this.options.videoClass + - '" data-type="' + - this.options.videoProvider + - '" data-code="' + - this.options.videoId + - '" data-width="100%" data-height="100%"></div>'); + this.element.find(this.options.container).append( + '<div class="' + + this.options.videoClass + + '" data-type="' + + this.options.videoProvider + + '" data-code="' + + this.options.videoId + + '" data-youtubenocookie="' + + this.options.useYoutubeNocookie + + '" data-width="100%" data-height="100%"></div>' + ); this.element.find(this.options.metaData.DOM.wrapper).show(); this.element.find(this.options.metaData.DOM.title).text(this.options.metaData.data.title); this.element.find(this.options.metaData.DOM.uploaded).text(this.options.metaData.data.uploaded); this.element.find(this.options.metaData.DOM.duration).text(this.options.metaData.data.duration); if (this.options.videoProvider === 'youtube') { - this.element.find(this.options.metaData.DOM.uploader).html( - '<a href="https://youtube.com/channel/' + - this.options.metaData.data.uploaderUrl + - '" target="_blank">' + - this.options.metaData.data.uploader + - '</a>' - ); + uploaderLinkUrl = 'https://youtube.com/channel/' + this.options.metaData.data.uploaderUrl; } else if (this.options.videoProvider === 'vimeo') { - this.element.find(this.options.metaData.DOM.uploader).html( - '<a href="' + - this.options.metaData.data.uploaderUrl + - '" target="_blank">' + this.options.metaData.data.uploader + - '</a>'); + uploaderLinkUrl = this.options.metaData.data.uploaderUrl; } + uploaderLink = document.createElement('a'); + uploaderLink.setAttribute('href', uploaderLinkUrl); + uploaderLink.setAttribute('target', '_blank'); + uploaderLink.innerText = this.options.metaData.data.uploader; + this.element.find(this.options.metaData.DOM.uploader)[0].appendChild(uploaderLink); this.element.find('.' + this.options.videoClass).productVideoLoader(); }, @@ -337,6 +340,7 @@ define([ .createVideoPlayer({ videoId: data.videoId, videoProvider: data.videoProvider, + useYoutubeNocookie: data.useYoutubeNocookie, reset: false, metaData: { DOM: { diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js index d70d2709474d5..3966321f6072c 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js @@ -34,7 +34,8 @@ define([ var id, type, ampersandPosition, - vimeoRegex; + vimeoRegex, + useYoutubeNocookie = false; /** * Get youtube ID @@ -68,9 +69,13 @@ define([ id = _getYoutubeId(id); type = 'youtube'; } - } else if (href.host.match(/youtube\.com|youtu\.be/)) { + } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) { id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, ''); type = 'youtube'; + + if (href.host.match(/youtube-nocookie.com/)) { + useYoutubeNocookie = true; + } } else if (href.host.match(/vimeo\.com/)) { type = 'vimeo'; vimeoRegex = new RegExp(['https?:\\/\\/(?:www\\.|player\\.)?vimeo.com\\/(?:channels\\/(?:\\w+\\/)', @@ -85,7 +90,7 @@ define([ } return id ? { - id: id, type: type, s: href.search.replace(/^\?/, '') + id: id, type: type, s: href.search.replace(/^\?/, ''), useYoutubeNocookie: useYoutubeNocookie } : false; } @@ -283,6 +288,7 @@ define([ tmpVideoData.id = dataUrl.id; tmpVideoData.provider = dataUrl.type; tmpVideoData.videoUrl = tmpInputData.videoUrl; + tmpVideoData.useYoutubeNocookie = dataUrl.useYoutubeNocookie; } videoData.push(tmpVideoData); @@ -631,6 +637,8 @@ define([ videoData.provider + '" data-code="' + videoData.id + + '" data-youtubenocookie="' + + videoData.useYoutubeNocookie + '" data-width="100%" data-height="100%"></div>' ); }, diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js b/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js index 3519d538e523a..75a2c1d75da15 100644 --- a/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js +++ b/app/code/Magento/ProductVideo/view/frontend/web/js/load-player.js @@ -88,6 +88,7 @@ define(['jquery', 'jquery/ui'], function ($) { this._playing = this._autoplay || false; this._loop = this.element.data('loop'); this._rel = this.element.data('related'); + this.useYoutubeNocookie = this.element.data('youtubenocookie') || false; this._responsive = this.element.data('responsive') !== false; @@ -164,6 +165,12 @@ define(['jquery', 'jquery/ui'], function ($) { * Handle event */ 'youtubeapiready': function () { + var host = 'https://www.youtube.com'; + + if (self.useYoutubeNocookie) { + host = 'https://www.youtube-nocookie.com'; + } + if (self._player !== undefined) { return; } @@ -182,6 +189,7 @@ define(['jquery', 'jquery/ui'], function ($) { width: self._width, videoId: self._code, playerVars: self._params, + host: host, events: { /** diff --git a/app/code/Magento/Quote/Model/BillingAddressManagement.php b/app/code/Magento/Quote/Model/BillingAddressManagement.php index 2cbca917c26a1..c89fc37b44689 100644 --- a/app/code/Magento/Quote/Model/BillingAddressManagement.php +++ b/app/code/Magento/Quote/Model/BillingAddressManagement.php @@ -76,6 +76,7 @@ public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $addres { /** @var \Magento\Quote\Model\Quote $quote */ $quote = $this->quoteRepository->getActive($cartId); + $address->setCustomerId($quote->getCustomerId()); $quote->removeAddress($quote->getBillingAddress()->getId()); $quote->setBillingAddress($address); try { diff --git a/app/code/Magento/Quote/Model/Product/Plugin/MarkQuotesRecollectMassDisabled.php b/app/code/Magento/Quote/Model/Product/Plugin/MarkQuotesRecollectMassDisabled.php new file mode 100644 index 0000000000000..f18bb46fa63fb --- /dev/null +++ b/app/code/Magento/Quote/Model/Product/Plugin/MarkQuotesRecollectMassDisabled.php @@ -0,0 +1,55 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Quote\Model\Product\Plugin; + +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Action as ProductAction; +use Magento\Quote\Model\ResourceModel\Quote as QuoteResource; + +/** + * Remove quote items after mass disabling products + */ +class MarkQuotesRecollectMassDisabled +{ + /** @var QuoteResource$quoteResource */ + private $quoteResource; + + /** + * @param QuoteResource $quoteResource + */ + public function __construct( + QuoteResource $quoteResource + ) { + $this->quoteResource = $quoteResource; + } + + /** + * Clean quote items after mass disabling product + * + * @param \Magento\Catalog\Model\Product\Action $subject + * @param \Magento\Catalog\Model\Product\Action $result + * @param int[] $productIds + * @param int[] $attrData + * @param int $storeId + * @return \Magento\Catalog\Model\Product\Action + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterUpdateAttributes( + ProductAction $subject, + ProductAction $result, + $productIds, + $attrData, + $storeId + ): ProductAction { + if (isset($attrData['status']) && $attrData['status'] === Status::STATUS_DISABLED) { + $this->quoteResource->markQuotesRecollect($productIds); + } + + return $result; + } +} diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php index b7ce25d56fd1f..640f89844546e 100644 --- a/app/code/Magento/Quote/Model/Quote.php +++ b/app/code/Magento/Quote/Model/Quote.php @@ -1313,8 +1313,7 @@ public function addAddress(\Magento\Quote\Api\Data\AddressInterface $address) */ public function setBillingAddress(\Magento\Quote\Api\Data\AddressInterface $address = null) { - $old = $this->getBillingAddress(); - + $old = $this->getAddressesCollection()->getItemById($address->getId()) ?? $this->getBillingAddress(); if (!empty($old)) { $old->addData($address->getData()); } else { @@ -1334,7 +1333,7 @@ public function setShippingAddress(\Magento\Quote\Api\Data\AddressInterface $add if ($this->getIsMultiShipping()) { $this->addAddress($address->setAddressType(Address::TYPE_SHIPPING)); } else { - $old = $this->getShippingAddress(); + $old = $this->getAddressesCollection()->getItemById($address->getId()) ?? $this->getShippingAddress(); if (!empty($old)) { $old->addData($address->getData()); } else { @@ -2302,7 +2301,7 @@ public function isVirtual() */ public function getIsVirtual() { - return intval($this->isVirtual()); + return (int)$this->isVirtual(); } /** diff --git a/app/code/Magento/Quote/Model/Quote/TotalsCollector.php b/app/code/Magento/Quote/Model/Quote/TotalsCollector.php index 8fa03232a0e8d..8f18a04a102fa 100644 --- a/app/code/Magento/Quote/Model/Quote/TotalsCollector.php +++ b/app/code/Magento/Quote/Model/Quote/TotalsCollector.php @@ -203,11 +203,12 @@ protected function _validateCouponCode(\Magento\Quote\Model\Quote $quote) */ protected function _collectItemsQtys(\Magento\Quote\Model\Quote $quote) { + $quoteItems = $quote->getAllVisibleItems(); $quote->setItemsCount(0); $quote->setItemsQty(0); $quote->setVirtualItemsQty(0); - foreach ($quote->getAllVisibleItems() as $item) { + foreach ($quoteItems as $item) { if ($item->getParentItem()) { continue; } diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php index c7525754dffd9..309c89e3702f5 100644 --- a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php +++ b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php @@ -3,9 +3,16 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Quote\Model\ResourceModel\Quote\Item; -use \Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; +use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus; +use Magento\Quote\Model\Quote; +use Magento\Quote\Model\Quote\Item as QuoteItem; +use Magento\Quote\Model\ResourceModel\Quote\Item as ResourceQuoteItem; /** * Quote item resource collection @@ -50,6 +57,11 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\VersionContro */ private $storeManager; + /** + * @var bool $recollectQuote + */ + private $recollectQuote = false; + /** * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory * @param \Psr\Log\LoggerInterface $logger @@ -102,7 +114,7 @@ public function __construct( */ protected function _construct() { - $this->_init(\Magento\Quote\Model\Quote\Item::class, \Magento\Quote\Model\ResourceModel\Quote\Item::class); + $this->_init(QuoteItem::class, ResourceQuoteItem::class); } /** @@ -110,7 +122,7 @@ protected function _construct() * * @return int */ - public function getStoreId() + public function getStoreId(): int { // Fallback to current storeId if no quote is provided // (see https://github.com/magento/magento2/commit/9d3be732a88884a66d667b443b3dc1655ddd0721) @@ -119,12 +131,12 @@ public function getStoreId() } /** - * Set Quote object to Collection + * Set Quote object to Collection. * - * @param \Magento\Quote\Model\Quote $quote + * @param Quote $quote * @return $this */ - public function setQuote($quote) + public function setQuote($quote): self { $this->_quote = $quote; $quoteId = $quote->getId(); @@ -138,14 +150,15 @@ public function setQuote($quote) } /** - * Reset the collection and inner join it to quotes table + * Reset the collection and inner join it to quotes table. + * * Optionally can select items with specified product id only * * @param string $quotesTableName * @param int $productId * @return $this */ - public function resetJoinQuotes($quotesTableName, $productId = null) + public function resetJoinQuotes($quotesTableName, $productId = null): self { $this->getSelect()->reset()->from( ['qi' => $this->getResource()->getMainTable()], @@ -162,11 +175,11 @@ public function resetJoinQuotes($quotesTableName, $productId = null) } /** - * After load processing + * After load processing. * * @return $this */ - protected function _afterLoad() + protected function _afterLoad(): self { parent::_afterLoad(); @@ -195,11 +208,11 @@ protected function _afterLoad() } /** - * Add options to items + * Add options to items. * * @return $this */ - protected function _assignOptions() + protected function _assignOptions(): self { $itemIds = array_keys($this->_items); $optionCollection = $this->_itemOptionCollectionFactory->create()->addItemFilter($itemIds); @@ -213,12 +226,12 @@ protected function _assignOptions() } /** - * Add products to items and item options + * Add products to items and item options. * * @return $this * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - protected function _assignProducts() + protected function _assignProducts(): self { \Magento\Framework\Profiler::start('QUOTE:' . __METHOD__, ['group' => 'QUOTE', 'method' => __METHOD__]); $productCollection = $this->_productCollectionFactory->create()->setStoreId( @@ -230,7 +243,6 @@ protected function _assignProducts() ); $this->skipStockStatusFilter($productCollection); $productCollection->addOptionsToResult()->addStoreFilter()->addUrlRewrite(); - $this->addTierPriceData($productCollection); $this->_eventManager->dispatch( 'prepare_catalog_product_collection_prices', @@ -241,46 +253,30 @@ protected function _assignProducts() ['collection' => $productCollection] ); - $recollectQuote = false; foreach ($this as $item) { + /** @var ProductInterface $product */ $product = $productCollection->getItemById($item->getProductId()); - if ($product) { + $isValidProduct = $this->isValidProduct($product); + $qtyOptions = []; + if ($isValidProduct) { $product->setCustomOptions([]); - $qtyOptions = []; - $optionProductIds = []; - foreach ($item->getOptions() as $option) { - /** - * Call type-specific logic for product associated with quote item - */ - $product->getTypeInstance()->assignProductToOption( - $productCollection->getItemById($option->getProductId()), - $option, - $product - ); - - if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) { - $optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId(); + $optionProductIds = $this->getOptionProductIds($item, $product, $productCollection); + foreach ($optionProductIds as $optionProductId) { + $qtyOption = $item->getOptionByCode('product_qty_' . $optionProductId); + if ($qtyOption) { + $qtyOptions[$optionProductId] = $qtyOption; } } - - if ($optionProductIds) { - foreach ($optionProductIds as $optionProductId) { - $qtyOption = $item->getOptionByCode('product_qty_' . $optionProductId); - if ($qtyOption) { - $qtyOptions[$optionProductId] = $qtyOption; - } - } - } - - $item->setQtyOptions($qtyOptions)->setProduct($product); } else { $item->isDeleted(true); - $recollectQuote = true; + $this->recollectQuote = true; + } + if (!$item->isDeleted()) { + $item->setQtyOptions($qtyOptions)->setProduct($product); + $item->checkData(); } - $item->checkData(); } - - if ($recollectQuote && $this->_quote) { + if ($this->recollectQuote && $this->_quote) { $this->_quote->collectTotals(); } \Magento\Framework\Profiler::stop('QUOTE:' . __METHOD__); @@ -289,31 +285,67 @@ protected function _assignProducts() } /** - * Prevents adding stock status filter to the collection of products. + * Get product Ids from option. * + * @param QuoteItem $item + * @param ProductInterface $product * @param ProductCollection $productCollection - * @return void + * @return array + */ + private function getOptionProductIds( + QuoteItem $item, + ProductInterface $product, + ProductCollection $productCollection + ): array { + $optionProductIds = []; + foreach ($item->getOptions() as $option) { + /** + * Call type-specific logic for product associated with quote item + */ + $product->getTypeInstance()->assignProductToOption( + $productCollection->getItemById($option->getProductId()), + $option, + $product + ); + + if (is_object($option->getProduct()) && $option->getProduct()->getId() != $product->getId()) { + $isValidProduct = $this->isValidProduct($option->getProduct()); + if (!$isValidProduct && !$item->isDeleted()) { + $item->isDeleted(true); + $this->recollectQuote = true; + continue; + } + $optionProductIds[$option->getProduct()->getId()] = $option->getProduct()->getId(); + } + } + + return $optionProductIds; + } + + /** + * Check is valid product. * - * @see \Magento\CatalogInventory\Helper\Stock::addIsInStockFilterToCollection + * @param ProductInterface $product + * @return bool */ - private function skipStockStatusFilter(ProductCollection $productCollection) + private function isValidProduct(ProductInterface $product): bool { - $productCollection->setFlag('has_stock_status_filter', true); + $result = ($product && (int)$product->getStatus() !== ProductStatus::STATUS_DISABLED); + + return $result; } /** - * Add tier prices to product collection. + * Prevents adding stock status filter to the collection of products. * * @param ProductCollection $productCollection * @return void + * + * @see \Magento\CatalogInventory\Helper\Stock::addIsInStockFilterToCollection */ - private function addTierPriceData(ProductCollection $productCollection) + private function skipStockStatusFilter(ProductCollection $productCollection) { - if (empty($this->_quote)) { - $productCollection->addTierPriceData(); - } else { - $productCollection->addTierPriceDataByGroupId($this->_quote->getCustomerGroupId()); - } + $productCollection->setFlag('has_stock_status_filter', true); } /** diff --git a/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml b/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml new file mode 100644 index 0000000000000..dba4a94f3db2a --- /dev/null +++ b/app/code/Magento/Quote/Test/Mftf/ActionGroup/ChangeStatusProductUsingProductGridActionGroup.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <!--Disabled a product by filtering grid and using change status action--> + <actionGroup name="ChangeStatusProductUsingProductGridActionGroup"> + <arguments> + <argument name="product"/> + <argument name="status" defaultValue="Enable" type="string" /> + </arguments> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad time="60" stepKey="waitForPageLoadInitial"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="openProductFilters"/> + <fillField selector="{{AdminProductGridFilterSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillProductSkuFilter"/> + <click selector="{{AdminProductGridFilterSection.applyFilters}}" stepKey="clickApplyFilters"/> + <see selector="{{AdminProductGridSection.productGridCell('1', 'SKU')}}" userInput="{{product.sku}}" stepKey="seeProductSkuInGrid"/> + <click selector="{{AdminProductGridSection.multicheckDropdown}}" stepKey="openMulticheckDropdown"/> + <click selector="{{AdminProductGridSection.multicheckOption('Select All')}}" stepKey="selectAllProductInFilteredGrid"/> + + <click selector="{{AdminProductGridSection.bulkActionDropdown}}" stepKey="clickActionDropdown"/> + <click selector="{{AdminProductGridSection.bulkActionOption('Change status')}}" stepKey="clickChangeStatusAction"/> + <click selector="{{AdminProductGridSection.changeStatus('status')}}" stepKey="clickChangeStatusDisabled" parameterized="true"/> + <see selector="{{AdminMessagesSection.success}}" userInput="A total of 1 record(s) have been updated." stepKey="seeSuccessMessage"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial2"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml b/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml new file mode 100644 index 0000000000000..32ac73aca7c03 --- /dev/null +++ b/app/code/Magento/Quote/Test/Mftf/Section/AdminProductGridSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminProductGridSection"> + <element name="changeStatus" selector="//div[contains(@class,'admin__data-grid-header-row') and contains(@class, 'row')]//div[contains(@class, 'action-menu-item')]//ul/li/span[text() = '{{status}}']" stepKey="clickChangeStatus" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml new file mode 100644 index 0000000000000..0c20eb54d5446 --- /dev/null +++ b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontGuestCheckoutDisabledProductTest"> + <annotations> + <features value="Checkout"/> + <stories value="Checkout via the Storefront"/> + <title value="Remove item from cart if product disabled"/> + <description value="Remove item from cart if simple or configurable product is disabled"/> + <severity value="MAJOR"/> + <testCaseId value="MAGETWO-95844"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="_defaultProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create the configurable product based on the data in the /data folder --> + <createData entity="BaseConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Make the configurable product have two options, that are children of the default attribute set --> + <createData entity="productAttributeWithDropdownTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <!-- Create the 2 children that will be a part of the configurable product --> + <createData entity="SimpleOption" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="SimpleOption" stepKey="createConfigChildProduct2"> + <field key="sku">SimpleTwoOption</field> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <!-- Assign the two products to the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + </after> + <!-- Step 1: Add simple product to shopping cart --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="amOnSimpleProductPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="StorefrontAddProductToCartActionGroup" stepKey="cartAddSimpleProductToCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="productCount" value="1"/> + </actionGroup> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct.custom_attributes[url_key]$$)}}" stepKey="goToConfigProductPage"/> + <waitForPageLoad stepKey="waitForStoreFrontLoad"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" userInput="$$getConfigAttributeOption1.value$$" stepKey="selectOption"/> + <click selector="{{StorefrontProductInfoMainSection.AddToCart}}" stepKey="clickAddToCart" /> + <waitForElement selector="{{StorefrontMessagesSection.messageProductAddedToCart($$createConfigProduct.name$$)}}" time="30" stepKey="assertMessage"/> + <!--Disabled via admin panel--> + <openNewTab stepKey="openNewTab"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Find the first simple product that we just created using the product grid and go to its page--> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="visitAdminProductPage"/> + <waitForPageLoad stepKey="waitForAdminProductGridLoad"/> + <conditionalClick selector="{{AdminProductGridFilterSection.clearFilters}}" dependentSelector="{{AdminProductGridFilterSection.clearFilters}}" visible="true" stepKey="clickClearFiltersInitial"/> + <actionGroup ref="filterProductGridBySku" stepKey="findCreatedProduct"> + <argument name="product" value="$$createConfigChildProduct1$$"/> + </actionGroup> + <waitForPageLoad stepKey="waitForFiltersToBeApplied"/> + <click selector="{{AdminProductGridSection.firstRow}}" stepKey="clickOnProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <!-- Disabled child configurable product --> + <click selector="{{AdminProductFormSection.enableProductAttributeLabel}}" stepKey="clickDisableProduct"/> + <click selector="{{AdminProductFormActionSection.saveButton}}" stepKey="clickSaveProduct"/> + <waitForPageLoad stepKey="waitForProductPageSaved"/> + <!-- Disabled simple product from grid --> + <actionGroup ref="ChangeStatusProductUsingProductGridActionGroup" stepKey="disabledProductFromGrid"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="status" value="Disable"/> + </actionGroup> + <closeTab stepKey="closeTab"/> + <!--Check cart--> + <reloadPage stepKey="reloadPage"/> + <waitForPageLoad stepKey="waitForCheckoutPageReload2"/> + <click selector="{{StorefrontMiniCartSection.show}}" stepKey="clickMiniCart"/> + <dontSeeElement selector="{{StorefrontMiniCartSection.quantity}}" stepKey="dontSeeCartItem"/> + </test> +</tests> diff --git a/app/code/Magento/Quote/Test/Mftf/composer.json b/app/code/Magento/Quote/Test/Mftf/composer.json deleted file mode 100644 index 56d05989a73a9..0000000000000 --- a/app/code/Magento/Quote/Test/Mftf/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "magento/functional-test-module-quote", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-sales-sequence": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-webapi": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Quote/composer.json b/app/code/Magento/Quote/composer.json index 9481442963d6b..0d67eaaf6ec74 100644 --- a/app/code/Magento/Quote/composer.json +++ b/app/code/Magento/Quote/composer.json @@ -23,7 +23,7 @@ "magento/module-webapi": "100.2.*" }, "type": "magento2-module", - "version": "101.0.4", + "version": "101.0.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Quote/etc/di.xml b/app/code/Magento/Quote/etc/di.xml index ecc6426855679..8add3786eb9a3 100644 --- a/app/code/Magento/Quote/etc/di.xml +++ b/app/code/Magento/Quote/etc/di.xml @@ -96,6 +96,9 @@ <plugin name="clean_quote_items_after_product_delete" type="Magento\Quote\Model\Product\Plugin\RemoveQuoteItems"/> <plugin name="update_quote_items_after_product_save" type="Magento\Quote\Model\Product\Plugin\UpdateQuoteItems"/> </type> + <type name="Magento\Catalog\Model\Product\Action"> + <plugin name="quoteProductMassChange" type="Magento\Quote\Model\Product\Plugin\MarkQuotesRecollectMassDisabled"/> + </type> <type name="Magento\Quote\Model\ValidationRules\QuoteValidationComposite"> <arguments> <argument name="validationRules" xsi:type="array"> diff --git a/app/code/Magento/QuoteAnalytics/Test/Mftf/composer.json b/app/code/Magento/QuoteAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 54c95cec8b1f9..0000000000000 --- a/app/code/Magento/QuoteAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-quote-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-quote": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/QuoteAnalytics/composer.json b/app/code/Magento/QuoteAnalytics/composer.json index 952b0b6a950ce..65978de6d0785 100644 --- a/app/code/Magento/QuoteAnalytics/composer.json +++ b/app/code/Magento/QuoteAnalytics/composer.json @@ -7,7 +7,7 @@ "magento/module-quote": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/ReleaseNotification/Test/Mftf/composer.json b/app/code/Magento/ReleaseNotification/Test/Mftf/composer.json deleted file mode 100644 index a6f19b330ec34..0000000000000 --- a/app/code/Magento/ReleaseNotification/Test/Mftf/composer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "magento/functional-test-module-release-notification", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-user": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ReleaseNotification/composer.json b/app/code/Magento/ReleaseNotification/composer.json index 7b86ff3486965..45452997757cd 100644 --- a/app/code/Magento/ReleaseNotification/composer.json +++ b/app/code/Magento/ReleaseNotification/composer.json @@ -8,7 +8,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Reports/Test/Mftf/composer.json b/app/code/Magento/Reports/Test/Mftf/composer.json deleted file mode 100644 index c5c05a6a9aa95..0000000000000 --- a/app/code/Magento/Reports/Test/Mftf/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "magento/functional-test-module-reports", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev", - "magento/functional-test-module-review": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Reports/composer.json b/app/code/Magento/Reports/composer.json index d532ab9e8aad3..6b5a43f040109 100644 --- a/app/code/Magento/Reports/composer.json +++ b/app/code/Magento/Reports/composer.json @@ -22,7 +22,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/RequireJs/Test/Mftf/composer.json b/app/code/Magento/RequireJs/Test/Mftf/composer.json deleted file mode 100644 index 85b954b6a3834..0000000000000 --- a/app/code/Magento/RequireJs/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-require-js", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/RequireJs/composer.json b/app/code/Magento/RequireJs/composer.json index 84d088c4ccd92..4c6afcd77b4fa 100644 --- a/app/code/Magento/RequireJs/composer.json +++ b/app/code/Magento/RequireJs/composer.json @@ -6,7 +6,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Review/Block/Adminhtml/Rating/Detailed.php b/app/code/Magento/Review/Block/Adminhtml/Rating/Detailed.php index adad931da5a69..a02c998f856bd 100644 --- a/app/code/Magento/Review/Block/Adminhtml/Rating/Detailed.php +++ b/app/code/Magento/Review/Block/Adminhtml/Rating/Detailed.php @@ -121,9 +121,9 @@ public function getRating() )->setStoreFilter( $stores )->setPositionOrder()->load()->addOptionToItems(); - if (intval($this->getRequest()->getParam('id'))) { + if ((int)$this->getRequest()->getParam('id')) { $this->_voteCollection = $this->_votesFactory->create()->setReviewFilter( - intval($this->getRequest()->getParam('id')) + (int)$this->getRequest()->getParam('id') )->addOptionInfo()->load()->addRatingOptions(); } } diff --git a/app/code/Magento/Review/Block/Rating/Entity/Detailed.php b/app/code/Magento/Review/Block/Rating/Entity/Detailed.php index 0ce4f436ae704..72cc921f530bb 100644 --- a/app/code/Magento/Review/Block/Rating/Entity/Detailed.php +++ b/app/code/Magento/Review/Block/Rating/Entity/Detailed.php @@ -42,7 +42,7 @@ public function __construct( protected function _toHtml() { $entityId = $this->_request->getParam('id'); - if (intval($entityId) <= 0) { + if ((int)$entityId <= 0) { return ''; } diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/JsonProductInfo.php b/app/code/Magento/Review/Controller/Adminhtml/Product/JsonProductInfo.php index c6e9cc81d5814..42fc6bf734ab9 100644 --- a/app/code/Magento/Review/Controller/Adminhtml/Product/JsonProductInfo.php +++ b/app/code/Magento/Review/Controller/Adminhtml/Product/JsonProductInfo.php @@ -46,7 +46,7 @@ public function execute() { $response = new DataObject(); $id = $this->getRequest()->getParam('id'); - if (intval($id) > 0) { + if ((int) $id > 0) { $product = $this->productRepository->getById($id); $response->setId($id); $response->addData($product->getData()); diff --git a/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php b/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php index 830354796907f..99c963501a9d4 100644 --- a/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php +++ b/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php @@ -540,6 +540,16 @@ protected function _afterLoad() return $this; } + /** + * Not add store ids to items + * + * @return $this + */ + protected function prepareStoreId() + { + return $this; + } + /** * Add store data * diff --git a/app/code/Magento/Review/Test/Mftf/composer.json b/app/code/Magento/Review/Test/Mftf/composer.json deleted file mode 100644 index 6bd1e26a76b88..0000000000000 --- a/app/code/Magento/Review/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-review", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-newsletter": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-review-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Review/composer.json b/app/code/Magento/Review/composer.json index 1b1efcbe4e66a..c6c8c920ed138 100644 --- a/app/code/Magento/Review/composer.json +++ b/app/code/Magento/Review/composer.json @@ -18,7 +18,7 @@ "magento/module-review-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/ReviewAnalytics/Test/Mftf/composer.json b/app/code/Magento/ReviewAnalytics/Test/Mftf/composer.json deleted file mode 100644 index 059c81d536ba3..0000000000000 --- a/app/code/Magento/ReviewAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-review-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-review": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/ReviewAnalytics/composer.json b/app/code/Magento/ReviewAnalytics/composer.json index 85f401a5a8968..229b486e3f8d4 100644 --- a/app/code/Magento/ReviewAnalytics/composer.json +++ b/app/code/Magento/ReviewAnalytics/composer.json @@ -7,7 +7,7 @@ "magento/module-review": "100.2.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Robots/Test/Mftf/composer.json b/app/code/Magento/Robots/Test/Mftf/composer.json deleted file mode 100644 index 5c17dbf9a4a27..0000000000000 --- a/app/code/Magento/Robots/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-robots", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-store": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-theme": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Robots/composer.json b/app/code/Magento/Robots/composer.json index 8230b11736796..ccf8356419581 100644 --- a/app/code/Magento/Robots/composer.json +++ b/app/code/Magento/Robots/composer.json @@ -10,7 +10,7 @@ "magento/module-theme": "100.2.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Rss/Test/Mftf/composer.json b/app/code/Magento/Rss/Test/Mftf/composer.json deleted file mode 100644 index 6c2c24759bd23..0000000000000 --- a/app/code/Magento/Rss/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-rss", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-customer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Rss/composer.json b/app/code/Magento/Rss/composer.json index 31b345d66ee81..d75274856e793 100644 --- a/app/code/Magento/Rss/composer.json +++ b/app/code/Magento/Rss/composer.json @@ -9,7 +9,7 @@ "magento/module-customer": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Rule/Test/Mftf/composer.json b/app/code/Magento/Rule/Test/Mftf/composer.json deleted file mode 100644 index 8de636383400a..0000000000000 --- a/app/code/Magento/Rule/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-rule", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Rule/composer.json b/app/code/Magento/Rule/composer.json index 1a12d67070021..2589db8fe86e6 100644 --- a/app/code/Magento/Rule/composer.json +++ b/app/code/Magento/Rule/composer.json @@ -11,7 +11,7 @@ "lib-libxml": "*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php index 6625f438f9515..fe1682e2de830 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Address.php @@ -217,6 +217,11 @@ public function getAddressCollectionJson() */ protected function _prepareForm() { + $storeId = $this->getCreateOrderModel() + ->getSession() + ->getStoreId(); + $this->_storeManager->setCurrentStore($storeId); + $fieldset = $this->_form->addFieldset('main', ['no_container' => true]); $addressForm = $this->_customerFormFactory->create('customer_address', 'adminhtml_customer_address'); diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php index 64b53d10d4af6..30b0872dfa90d 100644 --- a/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php +++ b/app/code/Magento/Sales/Block/Adminhtml/Order/View/Tab/History.php @@ -303,11 +303,7 @@ public static function sortHistoryByTimestamp($a, $b) $createdAtA = $a['created_at']; $createdAtB = $b['created_at']; - /** @var $createdAtA \DateTime */ - if ($createdAtA->getTimestamp() == $createdAtB->getTimestamp()) { - return 0; - } - return $createdAtA->getTimestamp() < $createdAtB->getTimestamp() ? -1 : 1; + return $createdAtA->getTimestamp() <=> $createdAtB->getTimestamp(); } /** diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php index 2ba8467ff6864..05a22245dc004 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassCancel.php @@ -5,10 +5,13 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; +use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Sales\Api\OrderManagementInterface; class MassCancel extends \Magento\Sales\Controller\Adminhtml\Order\AbstractMassAction { @@ -16,16 +19,43 @@ class MassCancel extends \Magento\Sales\Controller\Adminhtml\Order\AbstractMassA * Authorization level of a basic admin session */ const ADMIN_RESOURCE = 'Magento_Sales::cancel'; + + /** + * @var OrderManagementInterface + */ + private $orderManagement; /** * @param Context $context * @param Filter $filter * @param CollectionFactory $collectionFactory + * @param OrderManagementInterface|null $orderManagement */ - public function __construct(Context $context, Filter $filter, CollectionFactory $collectionFactory) - { + public function __construct( + Context $context, + Filter $filter, + CollectionFactory $collectionFactory, + OrderManagementInterface $orderManagement = null + ) { parent::__construct($context, $filter); $this->collectionFactory = $collectionFactory; + $this->orderManagement = $orderManagement ?: \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Sales\Api\OrderManagementInterface::class + ); + } + + /** + * @inheritDoc + */ + public function execute() + { + /** @var HttpRequest $request */ + $request = $this->getRequest(); + if (!$request->isPost()) { + throw new NotFoundException(__('Page not found.')); + } + + return parent::execute(); } /** @@ -38,11 +68,10 @@ protected function massAction(AbstractCollection $collection) { $countCancelOrder = 0; foreach ($collection->getItems() as $order) { - if (!$order->canCancel()) { + $isCanceled = $this->orderManagement->cancel($order->getEntityId()); + if ($isCanceled === false) { continue; } - $order->cancel(); - $order->save(); $countCancelOrder++; } $countNonCancelOrder = $collection->count() - $countCancelOrder; diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php index e894957dc8b6c..67263028d51a9 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassHold.php @@ -5,11 +5,13 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; +use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; use Magento\Sales\Api\OrderManagementInterface; +use Magento\Framework\App\Request\Http as HttpRequest; /** * Class MassHold @@ -43,6 +45,20 @@ public function __construct( $this->orderManagement = $orderManagement; } + /** + * @inheritDoc + */ + public function execute() + { + /** @var HttpRequest $request */ + $request = $this->getRequest(); + if (!$request->isPost()) { + throw new NotFoundException(__('Page not found.')); + } + + return parent::execute(); + } + /** * Hold selected orders * diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php index 2eb54c9814ef7..68090d7a75239 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/MassUnhold.php @@ -5,11 +5,13 @@ */ namespace Magento\Sales\Controller\Adminhtml\Order; +use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; use Magento\Backend\App\Action\Context; use Magento\Ui\Component\MassAction\Filter; use Magento\Sales\Model\ResourceModel\Order\CollectionFactory; use Magento\Sales\Api\OrderManagementInterface; +use Magento\Framework\App\Request\Http as HttpRequest; class MassUnhold extends AbstractMassAction { @@ -42,6 +44,20 @@ public function __construct( ); } + /** + * @inheritDoc + */ + public function execute() + { + /** @var HttpRequest $request */ + $request = $this->getRequest(); + if (!$request->isPost()) { + throw new NotFoundException(__('Page not found.')); + } + + return parent::execute(); + } + /** * Unhold selected orders * diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index c30bb5e328e8b..12d2a5396ddc4 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -16,6 +16,7 @@ use Magento\Quote\Model\Quote\Item; use Magento\Sales\Api\Data\OrderAddressInterface; use Magento\Sales\Model\Order; +use Magento\Store\Model\StoreManagerInterface; /** * Order create model @@ -243,6 +244,11 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ */ private $dataObjectConverter; + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param \Magento\Framework\Event\ManagerInterface $eventManager @@ -274,6 +280,7 @@ class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\ * @param array $data * @param \Magento\Framework\Serialize\Serializer\Json|null $serializer * @param ExtensibleDataObjectConverter|null $dataObjectConverter + * @param StoreManagerInterface $storeManager * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -306,7 +313,8 @@ public function __construct( \Magento\Quote\Model\QuoteFactory $quoteFactory, array $data = [], \Magento\Framework\Serialize\Serializer\Json $serializer = null, - ExtensibleDataObjectConverter $dataObjectConverter = null + ExtensibleDataObjectConverter $dataObjectConverter = null, + StoreManagerInterface $storeManager = null ) { $this->_objectManager = $objectManager; $this->_eventManager = $eventManager; @@ -340,6 +348,7 @@ public function __construct( parent::__construct($data); $this->dataObjectConverter = $dataObjectConverter ?: ObjectManager::getInstance() ->get(ExtensibleDataObjectConverter::class); + $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class); } /** @@ -417,7 +426,8 @@ public function setRecollect($flag) /** * Recollect totals for customer cart. - * Set recollect totals flag for quote + * + * Set recollect totals flag for quote. * * @return $this */ @@ -1327,6 +1337,7 @@ protected function _createCustomerForm(\Magento\Customer\Api\Data\CustomerInterf /** * Set and validate Quote address + * * All errors added to _errors * * @param \Magento\Quote\Model\Quote\Address $address @@ -1530,6 +1541,8 @@ public function resetShippingMethod() */ public function collectShippingRates() { + $store = $this->getQuote()->getStore(); + $this->storeManager->setCurrentStore($store); $this->getQuote()->getShippingAddress()->setCollectShippingRates(true); $this->collectRates(); diff --git a/app/code/Magento/Sales/Model/Download.php b/app/code/Magento/Sales/Model/Download.php index 6d3ad8491253a..14395bb9afedd 100644 --- a/app/code/Magento/Sales/Model/Download.php +++ b/app/code/Magento/Sales/Model/Download.php @@ -78,7 +78,8 @@ public function downloadFile($info) $this->_fileFactory->create( $info['title'], ['value' => $this->_rootDir->getRelativePath($relativePath), 'type' => 'filename'], - $this->rootDirBasePath + $this->rootDirBasePath, + $info['type'] ); } diff --git a/app/code/Magento/Sales/Model/Order.php b/app/code/Magento/Sales/Model/Order.php index 0038106134b7b..a1bbd4856df16 100644 --- a/app/code/Magento/Sales/Model/Order.php +++ b/app/code/Magento/Sales/Model/Order.php @@ -16,7 +16,7 @@ use Magento\Sales\Model\ResourceModel\Order\Address\Collection; use Magento\Sales\Model\ResourceModel\Order\Creditmemo\Collection as CreditmemoCollection; use Magento\Sales\Model\ResourceModel\Order\Invoice\Collection as InvoiceCollection; -use Magento\Sales\Model\ResourceModel\Order\Item\Collection as ImportCollection; +use Magento\Sales\Model\ResourceModel\Order\Item\Collection as ItemCollection; use Magento\Sales\Model\ResourceModel\Order\Payment\Collection as PaymentCollection; use Magento\Sales\Model\ResourceModel\Order\Shipment\Collection as ShipmentCollection; use Magento\Sales\Model\ResourceModel\Order\Shipment\Track\Collection as TrackCollection; @@ -1272,7 +1272,7 @@ public function addAddress(\Magento\Sales\Model\Order\Address $address) /** * @param array $filterByTypes * @param bool $nonChildrenOnly - * @return ImportCollection + * @return ItemCollection */ public function getItemsCollection($filterByTypes = [], $nonChildrenOnly = false) { @@ -1297,7 +1297,7 @@ public function getItemsCollection($filterByTypes = [], $nonChildrenOnly = false * Get random items collection without related children * * @param int $limit - * @return ImportCollection + * @return ItemCollection */ public function getParentItemsRandomCollection($limit = 1) { @@ -1309,7 +1309,7 @@ public function getParentItemsRandomCollection($limit = 1) * * @param int $limit * @param bool $nonChildrenOnly - * @return ImportCollection + * @return ItemCollection */ protected function _getItemsRandomCollection($limit, $nonChildrenOnly = false) { @@ -1787,7 +1787,7 @@ public function getTracksCollection() */ public function hasInvoices() { - return boolval($this->getInvoiceCollection()->count()); + return (bool)$this->getInvoiceCollection()->count(); } /** @@ -1797,7 +1797,7 @@ public function hasInvoices() */ public function hasShipments() { - return boolval($this->getShipmentsCollection()->count()); + return (bool)$this->getShipmentsCollection()->count(); } /** @@ -1807,7 +1807,7 @@ public function hasShipments() */ public function hasCreditmemos() { - return boolval($this->getCreditmemosCollection()->count()); + return (bool)$this->getCreditmemosCollection()->count(); } /** diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php index f00334f496b2a..f644d0c3a5a63 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Shipping.php @@ -37,6 +37,8 @@ public function __construct( } /** + * Collects credit memo shipping totals. + * * @param \Magento\Sales\Model\Order\Creditmemo $creditmemo * @return $this * @throws \Magento\Framework\Exception\LocalizedException @@ -55,12 +57,10 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo) $orderShippingInclTax = $order->getShippingInclTax(); $orderBaseShippingInclTax = $order->getBaseShippingInclTax(); $allowedTaxAmount = $order->getShippingTaxAmount() - $order->getShippingTaxRefunded(); - $baseAllowedTaxAmount = $order->getBaseShippingTaxAmount() - $order->getBaseShippingTaxRefunded(); $allowedAmountInclTax = $allowedAmount + $allowedTaxAmount; - $baseAllowedAmountInclTax = $baseAllowedAmount + $baseAllowedTaxAmount; - - // for the credit memo - $shippingAmount = $baseShippingAmount = $shippingInclTax = $baseShippingInclTax = 0; + $baseAllowedAmountInclTax = $orderBaseShippingInclTax + - $order->getBaseShippingRefunded() + - $order->getBaseShippingTaxRefunded(); // Check if the desired shipping amount to refund was specified (from invoice or another source). if ($creditmemo->hasBaseShippingAmount()) { @@ -128,7 +128,6 @@ private function isSuppliedShippingAmountInclTax($order) /** * Get the Tax Config. - * In a future release, will become a constructor parameter. * * @return \Magento\Tax\Model\Config * diff --git a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php index a842c0470ad85..5ab9469441bef 100644 --- a/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php +++ b/app/code/Magento/Sales/Model/Order/Creditmemo/Total/Tax.php @@ -5,9 +5,14 @@ */ namespace Magento\Sales\Model\Order\Creditmemo\Total; +/** + * Collects credit memo taxes. + */ class Tax extends AbstractTotal { /** + * Collects credit memo taxes. + * * @param \Magento\Sales\Model\Order\Creditmemo $creditmemo * @return $this * @@ -79,18 +84,10 @@ public function collect(\Magento\Sales\Model\Order\Creditmemo $creditmemo) $totalDiscountTaxCompensation += $invoice->getShippingDiscountTaxCompensationAmount() * $taxFactor; $baseTotalDiscountTaxCompensation += $invoice->getBaseShippingDiscountTaxCompensationAmnt() * $taxFactor; - $shippingDiscountTaxCompensationAmount = - $invoice->getShippingDiscountTaxCompensationAmount() * $taxFactor; - $baseShippingDiscountTaxCompensationAmount = - $invoice->getBaseShippingDiscountTaxCompensationAmnt() * $taxFactor; $shippingTaxAmount = $creditmemo->roundPrice($shippingTaxAmount); $baseShippingTaxAmount = $creditmemo->roundPrice($baseShippingTaxAmount, 'base'); $totalDiscountTaxCompensation = $creditmemo->roundPrice($totalDiscountTaxCompensation); $baseTotalDiscountTaxCompensation = $creditmemo->roundPrice($baseTotalDiscountTaxCompensation, 'base'); - $shippingDiscountTaxCompensationAmount = - $creditmemo->roundPrice($shippingDiscountTaxCompensationAmount); - $baseShippingDiscountTaxCompensationAmount = - $creditmemo->roundPrice($baseShippingDiscountTaxCompensationAmount, 'base'); if ($taxFactor < 1 && $invoice->getShippingTaxAmount() > 0) { $isPartialShippingRefunded = true; } diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender.php b/app/code/Magento/Sales/Model/Order/Email/Sender.php index 6d4480c4c45e0..564fd1e2a4b98 100644 --- a/app/code/Magento/Sales/Model/Order/Email/Sender.php +++ b/app/code/Magento/Sales/Model/Order/Email/Sender.php @@ -65,6 +65,8 @@ public function __construct( } /** + * Send order email if it is enabled in configuration. + * * @param Order $order * @return bool */ @@ -81,17 +83,21 @@ protected function checkAndSend(Order $order) try { $sender->send(); - $sender->sendCopyTo(); } catch (\Exception $e) { $this->logger->error($e->getMessage()); - return false; } - + try { + $sender->sendCopyTo(); + } catch (\Exception $e) { + $this->logger->error($e->getMessage()); + } return true; } /** + * Populate order email template with customer information. + * * @param Order $order * @return void */ @@ -113,6 +119,8 @@ protected function prepareTemplate(Order $order) } /** + * Create Sender object using appropriate template and identity. + * * @return Sender */ protected function getSender() @@ -126,6 +134,8 @@ protected function getSender() } /** + * Get template options. + * * @return array */ protected function getTemplateOptions() @@ -137,6 +147,8 @@ protected function getTemplateOptions() } /** + * Render shipping address into html. + * * @param Order $order * @return string|null */ @@ -148,6 +160,8 @@ protected function getFormattedShippingAddress($order) } /** + * Render billing address into html. + * * @param Order $order * @return string|null */ diff --git a/app/code/Magento/Sales/Model/Order/Invoice/Total/Tax.php b/app/code/Magento/Sales/Model/Order/Invoice/Total/Tax.php index fd5c015d9db4f..6e12f10f0c679 100644 --- a/app/code/Magento/Sales/Model/Order/Invoice/Total/Tax.php +++ b/app/code/Magento/Sales/Model/Order/Invoice/Total/Tax.php @@ -5,6 +5,9 @@ */ namespace Magento\Sales\Model\Order\Invoice\Total; +/** + * Collects invoice taxes. + */ class Tax extends AbstractTotal { /** @@ -69,11 +72,24 @@ public function collect(\Magento\Sales\Model\Order\Invoice $invoice) } } + $taxDiscountCompensationAmt = $totalDiscountTaxCompensation; + $baseTaxDiscountCompensationAmt = $baseTotalDiscountTaxCompensation; + $allowedDiscountTaxCompensation = $order->getDiscountTaxCompensationAmount() - + $order->getDiscountTaxCompensationInvoiced(); + $allowedBaseDiscountTaxCompensation = $order->getBaseDiscountTaxCompensationAmount() - + $order->getBaseDiscountTaxCompensationInvoiced(); + if ($this->_canIncludeShipping($invoice)) { $totalTax += $order->getShippingTaxAmount(); $baseTotalTax += $order->getBaseShippingTaxAmount(); $totalDiscountTaxCompensation += $order->getShippingDiscountTaxCompensationAmount(); $baseTotalDiscountTaxCompensation += $order->getBaseShippingDiscountTaxCompensationAmnt(); + + $allowedDiscountTaxCompensation += $order->getShippingDiscountTaxCompensationAmount() - + $order->getShippingDiscountTaxCompensationInvoiced(); + $allowedBaseDiscountTaxCompensation += $order->getBaseShippingDiscountTaxCompensationAmnt() - + $order->getBaseShippingDiscountTaxCompensationInvoiced(); + $invoice->setShippingTaxAmount($order->getShippingTaxAmount()); $invoice->setBaseShippingTaxAmount($order->getBaseShippingTaxAmount()); $invoice->setShippingDiscountTaxCompensationAmount($order->getShippingDiscountTaxCompensationAmount()); @@ -81,14 +97,6 @@ public function collect(\Magento\Sales\Model\Order\Invoice $invoice) } $allowedTax = $order->getTaxAmount() - $order->getTaxInvoiced(); $allowedBaseTax = $order->getBaseTaxAmount() - $order->getBaseTaxInvoiced(); - $allowedDiscountTaxCompensation = $order->getDiscountTaxCompensationAmount() + - $order->getShippingDiscountTaxCompensationAmount() - - $order->getDiscountTaxCompensationInvoiced() - - $order->getShippingDiscountTaxCompensationInvoiced(); - $allowedBaseDiscountTaxCompensation = $order->getBaseDiscountTaxCompensationAmount() + - $order->getBaseShippingDiscountTaxCompensationAmnt() - - $order->getBaseDiscountTaxCompensationInvoiced() - - $order->getBaseShippingDiscountTaxCompensationInvoiced(); if ($invoice->isLast()) { $totalTax = $allowedTax; @@ -107,8 +115,8 @@ public function collect(\Magento\Sales\Model\Order\Invoice $invoice) $invoice->setTaxAmount($totalTax); $invoice->setBaseTaxAmount($baseTotalTax); - $invoice->setDiscountTaxCompensationAmount($totalDiscountTaxCompensation); - $invoice->setBaseDiscountTaxCompensationAmount($baseTotalDiscountTaxCompensation); + $invoice->setDiscountTaxCompensationAmount($taxDiscountCompensationAmt); + $invoice->setBaseDiscountTaxCompensationAmount($baseTaxDiscountCompensationAmt); $invoice->setGrandTotal($invoice->getGrandTotal() + $totalTax + $totalDiscountTaxCompensation); $invoice->setBaseGrandTotal($invoice->getBaseGrandTotal() + $baseTotalTax + $baseTotalDiscountTaxCompensation); diff --git a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php index c7df909192d3b..a32e2f159c5d4 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/AbstractPdf.php @@ -640,11 +640,7 @@ protected function _sortTotalsList($a, $b) return 0; } - if ($a['sort_order'] == $b['sort_order']) { - return 0; - } - - return $a['sort_order'] > $b['sort_order'] ? 1 : -1; + return $a['sort_order'] <=> $b['sort_order']; } /** diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Shipment.php b/app/code/Magento/Sales/Model/Order/Pdf/Shipment.php index b171fccdeb05b..32a289c0f5fa8 100644 --- a/app/code/Magento/Sales/Model/Order/Pdf/Shipment.php +++ b/app/code/Magento/Sales/Model/Order/Pdf/Shipment.php @@ -150,11 +150,11 @@ public function getPdf($shipments = []) $this->_drawItem($item, $page, $order); $page = end($pdf->pages); } + if ($shipment->getStoreId()) { + $this->_localeResolver->revert(); + } } $this->_afterGetPdf(); - if ($shipment->getStoreId()) { - $this->_localeResolver->revert(); - } return $pdf; } diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php index f6dd8f8527a53..82c612c1a781d 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php @@ -35,4 +35,19 @@ public function __construct( ) { parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel); } + + /** + * @inheritdoc + */ + protected function _initSelect() + { + parent::_initSelect(); + + $tableDescription = $this->getConnection()->describeTable($this->getMainTable()); + foreach ($tableDescription as $columnInfo) { + $this->addFilterToMap($columnInfo['COLUMN_NAME'], 'main_table.' . $columnInfo['COLUMN_NAME']); + } + + return $this; + } } diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Collection.php index f004a1ee37e65..8758fc1da92d8 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Collection.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Shipment/Collection.php @@ -57,14 +57,16 @@ protected function _construct() } /** - * Used to emulate after load functionality for each item without loading them + * Unserialize packages in each item * * @return $this */ protected function _afterLoad() { - $this->walk('afterLoad'); + foreach ($this->_items as $item) { + $this->getResource()->unserializeFields($item); + } - return $this; + return parent::_afterLoad(); } } diff --git a/app/code/Magento/Sales/Model/Service/CreditmemoService.php b/app/code/Magento/Sales/Model/Service/CreditmemoService.php index 24f56c0dbd595..1173fa4b7eb5a 100644 --- a/app/code/Magento/Sales/Model/Service/CreditmemoService.php +++ b/app/code/Magento/Sales/Model/Service/CreditmemoService.php @@ -177,8 +177,8 @@ public function refund( $creditmemo->getOrder(), !$offlineRequested ); - $this->getOrderRepository()->save($order); $this->creditmemoRepository->save($creditmemo); + $this->getOrderRepository()->save($order); $connection->commit(); } catch (\Exception $e) { $connection->rollBack(); @@ -189,6 +189,8 @@ public function refund( } /** + * Checks if credit memo is available for refund. + * * @param \Magento\Sales\Api\Data\CreditmemoInterface $creditmemo * @return bool * @throws \Magento\Framework\Exception\LocalizedException @@ -211,7 +213,7 @@ protected function validateForRefund(\Magento\Sales\Api\Data\CreditmemoInterface throw new \Magento\Framework\Exception\LocalizedException( __( 'The most money available to refund is %1.', - $creditmemo->getOrder()->formatBasePrice($baseAvailableRefund) + $creditmemo->getOrder()->formatPriceTxt($baseAvailableRefund) ) ); } @@ -219,8 +221,9 @@ protected function validateForRefund(\Magento\Sales\Api\Data\CreditmemoInterface } /** - * @return \Magento\Sales\Model\Order\RefundAdapterInterface + * Initializes RefundAdapterInterface dependency. * + * @return \Magento\Sales\Model\Order\RefundAdapterInterface * @deprecated 100.1.3 */ private function getRefundAdapter() @@ -233,8 +236,9 @@ private function getRefundAdapter() } /** - * @return \Magento\Framework\App\ResourceConnection|mixed + * Initializes ResourceConnection dependency. * + * @return \Magento\Framework\App\ResourceConnection|mixed * @deprecated 100.1.3 */ private function getResource() @@ -247,8 +251,9 @@ private function getResource() } /** - * @return \Magento\Sales\Api\OrderRepositoryInterface + * Initializes OrderRepositoryInterface dependency. * + * @return \Magento\Sales\Api\OrderRepositoryInterface * @deprecated 100.1.3 */ private function getOrderRepository() @@ -261,8 +266,9 @@ private function getOrderRepository() } /** - * @return \Magento\Sales\Api\InvoiceRepositoryInterface + * Initializes InvoiceRepositoryInterface dependency. * + * @return \Magento\Sales\Api\InvoiceRepositoryInterface * @deprecated 100.1.3 */ private function getInvoiceRepository() diff --git a/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php index 7fc3dd4e9dbb7..eac46becac450 100644 --- a/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php +++ b/app/code/Magento/Sales/Model/Service/PaymentFailuresService.php @@ -7,6 +7,7 @@ use Magento\Backend\App\Area\FrontNameResolver; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\Translate\Inline\StateInterface; @@ -15,11 +16,14 @@ use Magento\Sales\Api\PaymentFailuresInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; +use Psr\Log\LoggerInterface; /** * Service is responsible for handling failed payment transactions. * * It depends on Stores > Configuration > Sales > Checkout > Payment Failed Emails configuration. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class PaymentFailuresService implements PaymentFailuresInterface { @@ -50,25 +54,33 @@ class PaymentFailuresService implements PaymentFailuresInterface */ private $cartRepository; + /** + * @var LoggerInterface + */ + private $logger; + /** * @param ScopeConfigInterface $scopeConfig * @param StateInterface $inlineTranslation * @param TransportBuilder $transportBuilder * @param TimezoneInterface $localeDate * @param CartRepositoryInterface $cartRepository + * @param LoggerInterface|null $logger */ public function __construct( ScopeConfigInterface $scopeConfig, StateInterface $inlineTranslation, TransportBuilder $transportBuilder, TimezoneInterface $localeDate, - CartRepositoryInterface $cartRepository + CartRepositoryInterface $cartRepository, + LoggerInterface $logger = null ) { $this->scopeConfig = $scopeConfig; $this->inlineTranslation = $inlineTranslation; $this->transportBuilder = $transportBuilder; $this->localeDate = $localeDate; $this->cartRepository = $cartRepository; + $this->logger = $logger ?: ObjectManager::getInstance()->get(LoggerInterface::class); } /** @@ -126,7 +138,11 @@ public function handle( ->addBcc($bcc) ->getTransport(); - $transport->sendMessage(); + try { + $transport->sendMessage(); + } catch (\Exception $e) { + $this->logger->critical($e->getMessage()); + } } $this->inlineTranslation->resume(); diff --git a/app/code/Magento/Sales/Plugin/ShippingLabelConverter.php b/app/code/Magento/Sales/Plugin/ShippingLabelConverter.php new file mode 100644 index 0000000000000..c70f8189f8c98 --- /dev/null +++ b/app/code/Magento/Sales/Plugin/ShippingLabelConverter.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Sales\Plugin; + +use Magento\Sales\Api\Data\ShipmentInterface; +use Magento\Sales\Api\Data\ShipmentSearchResultInterface; +use Magento\Sales\Api\ShipmentRepositoryInterface; +use Magento\Sales\Model\Order\Shipment; + +/** + * Plugin to convert shipping label from blob to base64encoded string. + */ +class ShippingLabelConverter +{ + /** + * Convert shipping label from blob to base64encoded string. + * + * @param ShipmentRepositoryInterface $shipmentRepository + * @param ShipmentSearchResultInterface $searchResult + * @return ShipmentSearchResultInterface + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGetList( + ShipmentRepositoryInterface $shipmentRepository, + ShipmentSearchResultInterface $searchResult + ): ShipmentSearchResultInterface { + /** @var Shipment $item */ + foreach ($searchResult->getItems() as $item) { + if ($item->getShippingLabel() !== null) { + $item->setShippingLabel(base64_encode($item->getShippingLabel())); + } + } + + return $searchResult; + } + + /** + * Convert shipping label from blob to base64encoded string. + * + * @param ShipmentRepositoryInterface $shipmentRepository + * @param ShipmentInterface $shipment + * @return ShipmentInterface + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterGet( + ShipmentRepositoryInterface $shipmentRepository, + ShipmentInterface $shipment + ): ShipmentInterface { + if ($shipment->getShippingLabel() !== null) { + $shipment->setShippingLabel(base64_encode($shipment->getShippingLabel())); + } + + return $shipment; + } +} diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml index 02f2e108e1dc8..a4ff621c05e1d 100644 --- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml @@ -56,6 +56,23 @@ <see selector="{{AdminOrderFormBillingAddressSection.phoneError}}" userInput="This is a required field." stepKey="seePhoneRequired"/> <see selector="{{AdminOrderFormPaymentSection.shippingError}}" userInput="This is a required field." stepKey="seeShippingMethodRequired"/> </actionGroup> + <!--Navigate to create order page (New Order -> Select Customer)--> + <actionGroup name="navigateToNewOrderPageExistingCustomer"> + <arguments> + <argument name="customer"/> + </arguments> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderIndexPage"/> + <waitForPageLoad stepKey="waitForIndexPageLoad"/> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Orders" stepKey="seeIndexPageTitle"/> + <click selector="{{AdminMainActionsSection.add}}" stepKey="clickCreateNewOrder"/> + <waitForPageLoad stepKey="waitForCustomerGridLoad"/> + <fillField userInput="{{customer.email}}" selector="{{AdminOrderCustomersGridSection.emailFilter}}" stepKey="filterEmail"/> + <click selector="{{AdminOrderCustomersGridSection.searchButton}}" stepKey="applyFilter"/> + <waitForPageLoad stepKey="waitForFilteredCustomerGridLoad"/> + <click selector="{{AdminOrderCustomersGridSection.firstRow}}" stepKey="clickOnCustomer"/> + <waitForPageLoad stepKey="waitForCreateOrderPageLoad" /> + <see selector="{{AdminHeaderSection.pageTitle}}" userInput="Create New Order" stepKey="seeNewOrderPageTitle"/> + </actionGroup> <!--Add a simple product to order--> <actionGroup name="addSimpleProductToOrder"> <arguments> @@ -70,6 +87,41 @@ <scrollTo selector="{{AdminOrderFormItemsSection.addSelected}}" x="0" y="-100" stepKey="scrollToAddSelectedButton"/> <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> </actionGroup> + <!--Add configurable product to order from Admin --> + <actionGroup name="addConfigurableProductToOrderFromAdmin"> + <arguments> + <argument name="product"/> + <argument name="attribute"/> + <argument name="option"/> + </arguments> + <click selector="{{AdminOrderFormItemsSection.addProducts}}" stepKey="clickAddProducts"/> + <fillField selector="{{AdminOrderFormItemsSection.skuFilter}}" userInput="{{product.sku}}" stepKey="fillSkuFilterConfigurable"/> + <click selector="{{AdminOrderFormItemsSection.search}}" stepKey="clickSearchConfigurable"/> + <scrollTo selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" x="0" y="-100" stepKey="scrollToCheckColumn"/> + <checkOption selector="{{AdminOrderFormItemsSection.rowCheck('1')}}" stepKey="selectConfigurableProduct"/> + <waitForElementVisible selector="{{AdminOrderFormConfigureProductSection.optionSelect(attribute.default_frontend_label)}}" stepKey="waitForConfigurablePopover"/> + <wait time="2" stepKey="waitForOptionsToLoad"/> + <selectOption selector="{{AdminOrderFormConfigureProductSection.optionSelect(attribute.default_frontend_label)}}" + userInput="{{option.label}}" stepKey="selectionConfigurableOption"/> + <click selector="{{AdminOrderFormConfigureProductSection.ok}}" stepKey="clickOkConfigurablePopover"/> + <scrollTo selector="{{AdminOrderFormItemsSection.addSelected}}" x="0" y="-100" stepKey="scrollToAddSelectedButton"/> + <click selector="{{AdminOrderFormItemsSection.addSelected}}" stepKey="clickAddSelectedProducts"/> + </actionGroup> + <!-- Change product configurations which was added to order --> + <actionGroup name="configureOrderedConfigurableProduct"> + <arguments> + <argument name="attribute"/> + <argument name="option"/> + <argument name="quantity" type="string"/> + </arguments> + <click selector="{{AdminOrderFormItemsSection.configure}}" stepKey="clickConfigure"/> + <waitForElementVisible selector="{{AdminOrderFormConfigureProductSection.optionSelect(attribute.default_frontend_label)}}" stepKey="waitForConfigurablePopover"/> + <wait time="2" stepKey="waitForOptionsToLoad"/> + <selectOption selector="{{AdminOrderFormConfigureProductSection.optionSelect(attribute.default_frontend_label)}}" + userInput="{{option.label}}" stepKey="selectionConfigurableOption"/> + <fillField selector="{{AdminOrderFormConfigureProductSection.quantity}}" userInput="{{quantity}}" stepKey="fillQuantity"/> + <click selector="{{AdminOrderFormConfigureProductSection.ok}}" stepKey="clickOkConfigurablePopover"/> + </actionGroup> <!--Fill customer billing address--> <actionGroup name="fillOrderCustomerInformation"> <arguments> @@ -105,6 +157,13 @@ <click selector="{{AdminOrderCustomersGridSection.customerRowByEmail(customer.email)}}" stepKey="chooseCustomer"/> <waitForPageLoad stepKey="waitForOrderPageLoad"/> </actionGroup> + <!--Verify order information--> + <actionGroup name="verifyCreatedOrderInformation"> + <see selector="{{AdminMessagesSection.success}}" userInput="You created the order." stepKey="seeSuccessMessage"/> + <see selector="{{AdminOrderDetailsInformationSection.orderStatus}}" userInput="Pending" after="seeSuccessMessage" stepKey="seeOrderPendingStatus"/> + <grabTextFrom selector="{{AdminOrderDetailsInformationSection.orderId}}" stepKey="getOrderId"/> + <assertNotEmpty actual="$getOrderId" stepKey="assertOrderIdIsNotEmpty"/> + </actionGroup> <!--Check that customer information is correct in order--> <actionGroup name="verifyBasicOrderInformation"> diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml new file mode 100644 index 0000000000000..65a4b512394d8 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <!--Filter order grid by order id field--> + <actionGroup name="filterOrderGridById"> + <arguments> + <argument name="orderId" type="string"/> + </arguments> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="navigateToOrderGridPage"/> + <waitForPageLoad stepKey="waitForOrderGridLoad"/> + <conditionalClick selector="{{AdminOrdersGridSection.clearFilters}}" dependentSelector="{{AdminOrdersGridSection.clearFilters}}" visible="true" stepKey="clearExistingOrderFilters"/> + <click selector="{{AdminOrdersGridSection.filters}}" stepKey="openOrderGridFilters"/> + <fillField selector="{{AdminOrdersGridSection.idFilter}}" userInput="{{orderId}}" stepKey="fillOrderIdFilter"/> + <click selector="{{AdminOrdersGridSection.applyFilters}}" stepKey="clickOrderApplyFilters"/> + </actionGroup> + <actionGroup name="AdminOrdersGridClearFiltersActionGroup"> + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="goToGridOrdersPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + <conditionalClick selector="{{AdminOrdersGridSection.clearFilters}}" dependentSelector="{{AdminOrdersGridSection.enabledFilters}}" visible="true" stepKey="clickOnButtonToRemoveFiltersIfPresent"/> + </actionGroup> +</actionGroups> 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..149850ffd3752 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/CreateOrderToPrintPageActionGroup.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + <actionGroup name="CreateOrderToPrintPageActionGroup"> + <arguments> + <argument name="Category"/> + </arguments> + <amOnPage url="{{StorefrontCategoryPage.url(Category.name)}}" stepKey="onCategoryPage"/> + <waitForPageLoad stepKey="waitForPageLoad1"/> + <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> + <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> + <click selector="{{StorefrontMiniCartSection.show}}" stepKey="clickCart"/> + <click selector="{{StorefrontMiniCartSection.goToCheckout}}" stepKey="goToCheckout"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <click selector="{{CheckoutShippingMethodsSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask2"/> + <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" stepKey="waitForPlaceOrderButton"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <click selector="{{CheckoutSuccessMainSection.orderLink}}" stepKey="clickOrderLink"/> + <click selector="{{StorefrontCustomerOrderViewSection.printOrderLink}}" stepKey="clickPrintOrderLink"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml index e51a6e62b352e..6522dbf3d25ce 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderCreatePage.xml @@ -9,6 +9,7 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> <page name="AdminOrderCreatePage" url="sales/order_create/index" area="admin" module="Magento_Sales"> + <section name="AdminOrderFormSelectWebsiteSection"/> <section name="AdminOrderFormActionSection"/> <section name="AdminOrderFormAccountSection"/> <section name="AdminOrderFormBillingAddressSection"/> @@ -16,5 +17,6 @@ <section name="AdminOrderFormItemsSection"/> <section name="AdminOrderFormTotalSection"/> <section name="AdminOrderCustomersGridSection"/> + <section name="AdminOrderFormConfigureProductSection"/> </page> </pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrdersPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrdersPage.xml index 8e24a6be6d9bf..be6d90031f42f 100644 --- a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrdersPage.xml +++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrdersPage.xml @@ -9,6 +9,7 @@ <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> <page name="AdminOrdersPage" url="/sales/order/" area="admin" module="Magento_Sales"> + <section name="AdminMainActionsSection"/> <section name="OrdersGridSection"/> </page> </pages> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="StorefrontSalesOrderPrintPage" url="/sales/order/print/order_id/{{var1}}/" parameterized="true" + area="storefront" module="Magento_Sales"> + <section name="SalesOrderPrintSection"/> + </page> +</pages> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml index 572b1314e81dc..065770791cd54 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderCustomersGridSection.xml @@ -14,5 +14,6 @@ <element name="resetButton" type="button" selector="#sales_order_create_customer_grid [data-action='grid-filter-reset']"/> <element name="searchButton" type="button" selector="#sales_order_create_customer_grid [data-action='grid-filter-apply']"/> <element name="emailFilter" type="input" selector="#sales_order_create_customer_grid_filter_email"/> + <element name="firstRow" type="button" selector="tr:nth-of-type(1)[data-role='row']"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml index bd53273b168c8..59e16908ff088 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderDetailsInformationSection.xml @@ -11,6 +11,7 @@ <section name="AdminOrderDetailsInformationSection"> <element name="orderDate" type="text" selector=".order-information table.order-information-table tr:first-of-type > td"/> <element name="orderStatus" type="text" selector=".order-information table.order-information-table #order_status"/> + <element name="orderId" type="text" selector="|Order # (\d+)|"/> <element name="purchasedFrom" type="text" selector=".order-information table.order-information-table tr:last-of-type > td"/> <element name="accountInformation" type="text" selector=".order-account-information-table"/> <element name="customerName" type="text" selector=".order-account-information table tr:first-of-type > td span"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormConfigureProductSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormConfigureProductSection.xml new file mode 100644 index 0000000000000..83d417f6f8555 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormConfigureProductSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminOrderFormConfigureProductSection"> + <element name="optionSelect" type="select" selector="//div[@class='product-options']/div/div/select[../../label[text() = '{{option}}']]" parameterized="true"/> + <element name="quantity" type="input" selector="#product_composite_configure_input_qty"/> + <element name="ok" type="button" selector=".modal-header .page-actions button[data-role='action']" timeout="30"/> + </section> +</sections> \ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml index d2b9e8bf6af55..79a897ec52a4c 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsSection.xml @@ -15,5 +15,15 @@ <element name="rowCheck" type="checkbox" selector="#sales_order_create_search_grid_table > tbody tr:nth-of-type({{row}}) td.col-select [type=checkbox]" parameterized="true"/> <element name="rowQty" type="input" selector="#sales_order_create_search_grid_table > tbody tr:nth-of-type({{row}}) td.col-qty [name='qty']" parameterized="true"/> <element name="addSelected" type="button" selector="#order-search .admin__page-section-title .actions button.action-add" timeout="30"/> + <element name="configure" type="button" selector=".product-configure-block button.action-default.scalable" timeout="30"/> + <element name="selectProduct" type="checkbox" selector="//td[contains(text(), '{{arg}}')]/following-sibling::td[contains(@class, 'col-select col-in_products')]" parameterized="true"/> + <element name="setQuantity" type="checkbox" selector="//td[contains(text(), '{{arg}}')]/following-sibling::td[contains(@class, 'col-qty')]/input" parameterized="true"/> + <element name="customPrice" type="checkbox" selector="//span[text()='{{arg}}']/parent::td/following-sibling::td/div//span[contains(text(),'Custom Price')]" parameterized="true"/> + <element name="customQuantity" type="input" selector="//span[text()='{{arg}}']/parent::td/following-sibling::td[@class='col-qty']/input" parameterized="true"/> + <element name="update" type="button" selector="//span[text()='Update Items and Quantities']"/> + <element name="discount" type="text" selector="//span[text()='{{arg}}']/parent::td/following-sibling::td[@class='col-discount col-price']/span" parameterized="true"/> + <element name="productPrice" type="text" selector="//span[text()='{{arg}}']/parent::td/following-sibling::td[@class='col-price col-row-subtotal']/span" parameterized="true"/> + <element name="removeItems" type="select" selector="//span[text()='{{arg}}']/parent::td/following-sibling::td/select[@class='admin__control-select']" parameterized="true"/> + <element name="applyCoupon" type="input" selector="#coupons:code"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml index 31744a09b052c..ab74320f26a30 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormPaymentSection.xml @@ -13,5 +13,6 @@ <element name="getShippingMethods" type="text" selector="#order-shipping_method a.action-default" timeout="30"/> <element name="flatRateOption" type="radio" selector="#s_method_flatrate_flatrate" timeout="30"/> <element name="shippingError" type="text" selector="#order[has_shipping]-error"/> + <element name="freeShippingOption" type="radio" selector="#s_method_freeshipping_freeshipping" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormSelectWebsiteSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormSelectWebsiteSection.xml new file mode 100644 index 0000000000000..b94c1dd1c5deb --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormSelectWebsiteSection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminOrderFormSelectWebsiteSection"> + <element name="website" type="radio" selector="//label[contains(text(), '{{arg}}')]" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml index 082eba016f898..f9e954548a4d0 100644 --- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrdersGridSection.xml @@ -11,6 +11,12 @@ <section name="AdminOrdersGridSection"> <element name="search" type="input" selector="#fulltext"/> <element name="submitSearch" type="button" selector=".//*[@id='container']/div/div[2]/div[1]/div[2]/button"/> - <element name="firstRow" type="button" selector="tr.data-row:nth-of-type(1)"/> + <element name="firstRow" type="button" selector="tr.data-row:nth-of-type(1)" timeout="30"/> + <element name="createNewOrder" type="button" selector=".page-actions-buttons button#add" timeout="30"/> + <element name="filters" type="button" selector="button[data-action='grid-filter-expand']" timeout="30"/> + <element name="idFilter" type="input" selector=".admin__data-grid-filters input[name='increment_id']"/> + <element name="applyFilters" type="button" selector="button[data-action='grid-filter-apply']" timeout="30"/> + <element name="clearFilters" type="button" selector=".admin__data-grid-header [data-action='grid-filter-reset']" timeout="30"/> + <element name="enabledFilters" type="block" selector=".admin__data-grid-header .admin__data-grid-filters-current._show"/> </section> </sections> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Section/TemplateSectionFile.xml b/app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml similarity index 77% rename from dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Section/TemplateSectionFile.xml rename to app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml index 4c1cd7300cf2f..b08a66140fabf 100644 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Section/TemplateSectionFile.xml +++ b/app/code/Magento/Sales/Test/Mftf/Section/SalesOrderPrintSection.xml @@ -8,7 +8,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - <section name=""> - <element name="" type="" selector=""/> + <section name="SalesOrderPrintSection"> + <element name="isOrderPrintPage" type="block" selector=".preview-area"/> </section> </sections> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml index eaad9912c6de1..82ac6d9515ce6 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml @@ -61,6 +61,7 @@ <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onAdminOrdersPage"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask3"/> + <actionGroup ref="clearFiltersAdminDataGrid" stepKey="clearGridFilter"/> <fillField selector="{{OrdersGridSection.search}}" userInput="{$grabOrderNumber}" stepKey="searchOrderNum"/> <click selector="{{OrdersGridSection.submitSearch}}" stepKey="submitSearch"/> <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask4"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml new file mode 100644 index 0000000000000..0bba86ae0ed87 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml @@ -0,0 +1,63 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest"> + <annotations> + <features value="Sales"/> + <stories value="Admin create order"/> + <title value="Free Shipping is not available in Admin if Minimum Order Amount does not match Order total"/> + <description value="Admin should not be able place order with Free Shipping method if Minimum Order Amount does not match Order total"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-77572"/> + <group value="sales"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="createProduct"> + <requiredEntity createDataKey="createCategory"/> + <field key="price">100</field> + </createData> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <createData entity="DisableFlatRateShippingMethodConfig" stepKey="disableFlatRate"/> + <createData entity="FreeShippinMethodConfig" stepKey="enableFreeShippingMethod"/> + <createData entity="setFreeShippingSubtotal" stepKey="setFreeShippingSubtotal"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </before> + <after> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <createData entity="FlatRateShippingMethodConfig" stepKey="enableFlatRate"/> + <createData entity="FreeShippinMethodDefault" stepKey="disableFreeShippingMethod"/> + <createData entity="setFreeShippingSubtotalToDefault" stepKey="setFreeShippingSubtotalToDefault"/> + <actionGroup ref="logout" stepKey="logout"/> + <magentoCLI command="cache:flush" stepKey="flushCache2"/> + </after> + <!--Create new order with existing customer--> + <actionGroup ref="navigateToNewOrderPageExistingCustomer" stepKey="goToCreateOrderPage"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + <!--Add product to order--> + <actionGroup ref="addSimpleProductToOrder" stepKey="addProductToOrder"> + <argument name="product" value="$$createProduct$$"/> + </actionGroup> + + <click selector="{{AdminOrderFormPaymentSection.header}}" stepKey="unfocus"/> + <waitForPageLoad stepKey="waitForJavascriptToFinish"/> + <!--Click *Get shipping methods and rates* and see that Free Shipping is absent--> + <click selector="{{AdminOrderFormPaymentSection.getShippingMethods}}" stepKey="clickGetShippingMehods"/> + <dontSeeElement selector="{{AdminOrderFormPaymentSection.freeShippingOption}}" stepKey="seeAbsentFreeShipping"/> + <!--Submit Order and verify that Order isn't placed--> + <click selector="{{AdminOrderFormActionSection.submitOrder}}" stepKey="clickSubmitOrder"/> + + <dontSeeElement selector="{{AdminMessagesSection.successMessage}}" stepKey="seeSuccessMessage"/> + <seeElement selector="{{AdminMessagesSection.errorMessage}}" stepKey="seeErrorMessage"/> + </test> +</tests> \ No newline at end of file diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml new file mode 100644 index 0000000000000..f2fcf7c59c829 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminSubmitConfigurableProductOrderTest"> + <annotations> + <title value="Create Order in Admin and update product configuration"/> + <stories value="MAGETWO-73883: Create Sales > Order from admin add configurable product and change options click OK does not update Items Ordered List"/> + <description value="Create Order in Admin and update product configuration"/> + <features value="Sales"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-77679"/> + <group value="Sales"/> + </annotations> + <before> + <!--Set default flat rate shipping method settings--> + <createData entity="FlatRateShippingMethodDefault" stepKey="setDefaultFlatRateShippingMethod"/> + <!--Create simple customer--> + <createData entity="Simple_US_CA_Customer" stepKey="createSimpleCustomer"/> + <!-- Create the category --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <!-- Create the configurable product and add it to the category --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create an attribute with two options to be used in the first child product --> + <createData entity="productAttributeWithDropdownTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <!-- Add the attribute we just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <!-- Get the option of the attribute we created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <!-- Create a simple product and give it the attribute with option --> + <createData entity="ApiSimpleOne" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="ApiSimpleTwo" stepKey="createConfigChildProduct2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <!-- Create the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <!-- Add simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + </before> + <!--Create new customer order--> + <actionGroup ref="navigateToNewOrderPageExistingCustomer" stepKey="navigateToNewOrderWithExistingCustomer"> + <argument name="customer" value="$$createSimpleCustomer$$"/> + </actionGroup> + <!--Add configurable product to order--> + <actionGroup ref="addConfigurableProductToOrderFromAdmin" stepKey="addConfigurableProductToOrder"> + <argument name="product" value="$$createConfigProduct$$"/> + <argument name="attribute" value="$$createConfigProductAttribute$$"/> + <argument name="option" value="$$getConfigAttributeOption1$$"/> + </actionGroup> + <!--Configure ordered configurable product--> + <actionGroup ref="configureOrderedConfigurableProduct" stepKey="configureOrderedConfigurableProduct"> + <argument name="attribute" value="$$createConfigProductAttribute$$"/> + <argument name="option" value="$$getConfigAttributeOption2$$"/> + <argument name="quantity" value="2"/> + </actionGroup> + <!--Select FlatRate shipping method--> + <actionGroup ref="orderSelectFlatRateShipping" stepKey="orderSelectFlatRateShippingMethod"/> + <!--Submit order--> + <click selector="{{AdminOrderFormActionSection.submitOrder}}" stepKey="submitOrder"/> + <!--Verify order information--> + <actionGroup ref="verifyCreatedOrderInformation" stepKey="verifyCreatedOrderInformation"/> + <after> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="createSimpleCustomer" stepKey="deleteSimpleCustomer"/> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> + </after> + </test> +</tests> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml index 4367e0845dccb..a703f5e690cfa 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml @@ -39,6 +39,7 @@ <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> <waitForPageLoad stepKey="waitForRulesPage"/> <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <waitForLoadingMaskToDisappear stepKey="waitForNewRulePageMask"/> <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{ApiSalesRule.name}}" stepKey="fillRuleName"/> <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsite"/> <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="chooseNotLoggedInCustomerGroup"/> 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..11279c4046003 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/Test/StorefrontRedirectToOrderHistory.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="StorefrontRedirectToOrderHistory"> + <annotations> + <features value="Redirection Rules"/> + <title value="Create Invoice"/> + <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="CustomerLoginOnStorefront" 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="CustomerLoginOnStorefront" 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/Test/Mftf/composer.json b/app/code/Magento/Sales/Test/Mftf/composer.json deleted file mode 100644 index 0d136bc5dadc9..0000000000000 --- a/app/code/Magento/Sales/Test/Mftf/composer.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "name": "magento/functional-test-module-sales", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/functional-test-module-sales-rule": "100.0.0-dev", - "magento/functional-test-module-sales-sequence": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-gift-message": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-wishlist": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-sales-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Address/FormTest.php b/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Address/FormTest.php index a35c3963f6993..15312d66825be 100644 --- a/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Address/FormTest.php +++ b/app/code/Magento/Sales/Test/Unit/Block/Adminhtml/Order/Address/FormTest.php @@ -20,6 +20,8 @@ use Magento\Sales\Model\Order; use Magento\Sales\Model\Order\Address; use PHPUnit_Framework_MockObject_MockObject as MockObject; +use Magento\Backend\Model\Session\Quote as QuoteSession; +use Magento\Sales\Model\AdminOrder\Create; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -51,6 +53,16 @@ class FormTest extends \PHPUnit\Framework\TestCase */ private $countriesCollection; + /** + * @var Create|MockObject + */ + private $orderCreate; + + /** + * @var QuoteSession|MockObject + */ + private $sessionQuote; + protected function setUp() { $objectManager = new ObjectManager($this); @@ -61,6 +73,15 @@ protected function setUp() $this->countriesCollection = $this->createMock( Collection::class ); + $this->sessionQuote = $this->getMockBuilder(QuoteSession::class) + ->disableOriginalConstructor() + ->setMethods(['getStoreId', 'getStore']) + ->getMock(); + $this->orderCreate = $this->getMockBuilder(Create::class) + ->disableOriginalConstructor() + ->getMock(); + $this->orderCreate->method('getSession') + ->willReturn($this->sessionQuote); $this->addressBlock = $objectManager->getObject( Form::class, @@ -69,6 +90,8 @@ protected function setUp() '_customerFormFactory' => $this->customerFormFactory, '_coreRegistry' => $this->coreRegistry, 'countriesCollection' => $this->countriesCollection, + 'sessionQuote' => $this->sessionQuote, + '_orderCreate' => $this->orderCreate ] ); } @@ -108,6 +131,8 @@ public function testGetForm() ->willReturn($order); $order->method('getStoreId') ->willReturn($storeId); + $this->sessionQuote->method('getStoreId') + ->willReturn($storeId); $this->countriesCollection->method('loadByStore') ->with($storeId) ->willReturn($this->countriesCollection); diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php index 569a210d993f0..53f2a070ead31 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassCancelTest.php @@ -85,6 +85,11 @@ class MassCancelTest extends \PHPUnit\Framework\TestCase */ protected $filterMock; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $orderManagementMock; + protected function setUp() { $objectManagerHelper = new ObjectManagerHelper($this); @@ -124,6 +129,7 @@ protected function setUp() ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) ->willReturn($redirectMock); + $this->requestMock->expects($this->once())->method('isPost')->willReturn(true); $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); @@ -145,12 +151,15 @@ protected function setUp() $this->orderCollectionFactoryMock->expects($this->once()) ->method('create') ->willReturn($this->orderCollectionMock); + $this->orderManagementMock = $this->createMock(\Magento\Sales\Api\OrderManagementInterface::class); + $this->massAction = $objectManagerHelper->getObject( \Magento\Sales\Controller\Adminhtml\Order\MassCancel::class, [ 'context' => $this->contextMock, 'filter' => $this->filterMock, - 'collectionFactory' => $this->orderCollectionFactoryMock + 'collectionFactory' => $this->orderCollectionFactoryMock, + 'orderManagement' => $this->orderManagementMock ] ); } @@ -161,6 +170,9 @@ protected function setUp() */ public function testExecuteCanCancelOneOrder() { + $order1id = 100; + $order2id = 200; + $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->disableOriginalConstructor() ->getMock(); @@ -175,20 +187,19 @@ public function testExecuteCanCancelOneOrder() ->willReturn($orders); $order1->expects($this->once()) - ->method('canCancel') - ->willReturn(true); - $order1->expects($this->once()) - ->method('cancel'); - $order1->expects($this->once()) - ->method('save'); + ->method('getEntityId') + ->willReturn($order1id); + + $order2->expects($this->once()) + ->method('getEntityId') + ->willReturn($order2id); $this->orderCollectionMock->expects($this->once()) ->method('count') ->willReturn($countOrders); - $order2->expects($this->once()) - ->method('canCancel') - ->willReturn(false); + $this->orderManagementMock->expects($this->at(0))->method('cancel')->with($order1id)->willReturn(true); + $this->orderManagementMock->expects($this->at(1))->method('cancel')->with($order2id)->willReturn(false); $this->messageManagerMock->expects($this->once()) ->method('addError') @@ -222,21 +233,23 @@ public function testExcludedCannotCancelOrders() $orders = [$order1, $order2]; $countOrders = count($orders); + $order1->expects($this->once()) + ->method('getEntityId') + ->willReturn(100); + + $order2->expects($this->once()) + ->method('getEntityId') + ->willReturn(200); + $this->orderCollectionMock->expects($this->any()) ->method('getItems') ->willReturn([$order1, $order2]); - $order1->expects($this->once()) - ->method('canCancel') - ->willReturn(false); - $this->orderCollectionMock->expects($this->once()) ->method('count') ->willReturn($countOrders); - $order2->expects($this->once()) - ->method('canCancel') - ->willReturn(false); + $this->orderManagementMock->expects($this->atLeastOnce())->method('cancel')->willReturn(false); $this->messageManagerMock->expects($this->once()) ->method('addError') @@ -265,11 +278,10 @@ public function testException() ->willReturn([$order1]); $order1->expects($this->once()) - ->method('canCancel') - ->willReturn(true); - $order1->expects($this->once()) - ->method('cancel') - ->willThrowException($exception); + ->method('getEntityId') + ->willReturn(100); + + $this->orderManagementMock->expects($this->atLeastOnce())->method('cancel')->willThrowException($exception); $this->messageManagerMock->expects($this->once()) ->method('addError') diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php deleted file mode 100644 index 02ff208445596..0000000000000 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassHoldTest.php +++ /dev/null @@ -1,253 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassHoldTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassHoldTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Sales\Controller\Adminhtml\Order\MassHold - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Backend\Model\Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionMock; - - /** - * @var \Magento\Framework\App\ActionFlag|\PHPUnit_Framework_MockObject_MockObject - */ - protected $actionFlagMock; - - /** - * @var \Magento\Backend\Helper\Data|\PHPUnit_Framework_MockObject_MockObject - */ - protected $helperMock; - - /** - * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \Magento\Sales\Api\OrderManagementInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderManagementMock; - - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - $this->orderManagementMock = $this->getMockBuilder(\Magento\Sales\Api\OrderManagementInterface::class) - ->getMockForAbstractClass(); - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor()->getMock(); - $this->objectManagerMock = $this->createPartialMock( - \Magento\Framework\ObjectManager\ObjectManager::class, - ['create'] - ); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - $this->orderCollectionMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $orderCollection = \Magento\Sales\Model\ResourceModel\Order\CollectionFactory::class; - $this->orderCollectionFactoryMock = $this->getMockBuilder($orderCollection) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - - $this->sessionMock = $this->createPartialMock(\Magento\Backend\Model\Session::class, ['setIsUrlNotice']); - $this->actionFlagMock = $this->createPartialMock(\Magento\Framework\App\ActionFlag::class, ['get', 'set']); - $this->helperMock = $this->createPartialMock(\Magento\Backend\Helper\Data::class, ['getUrl']); - $this->resultRedirectMock = $this->createMock(\Magento\Backend\Model\View\Result\Redirect::class); - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->once())->method('getSession')->willReturn($this->sessionMock); - $this->contextMock->expects($this->once())->method('getActionFlag')->willReturn($this->actionFlagMock); - $this->contextMock->expects($this->once())->method('getHelper')->willReturn($this->helperMock); - $this->contextMock - ->expects($this->once()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->orderCollectionMock) - ->willReturn($this->orderCollectionMock); - $this->orderCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->orderCollectionMock); - - $this->massAction = $objectManagerHelper->getObject( - \Magento\Sales\Controller\Adminhtml\Order\MassHold::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->orderCollectionFactoryMock, - 'orderManagement' => $this->orderManagementMock - ] - ); - } - - public function testExecuteOneOrderPutOnHold() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - $countOrders = count($orders); - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('canHold') - ->willReturn(true); - $this->orderManagementMock->expects($this->once()) - ->method('hold'); - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn($countOrders); - - $order2->expects($this->once()) - ->method('canHold') - ->willReturn(false); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('1 order(s) were not put on hold.'); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with('You have put 1 order(s) on hold.'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - public function testExecuteNoOrdersPutOnHold() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - $countOrders = count($orders); - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('canHold') - ->willReturn(false); - - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn($countOrders); - - $order2->expects($this->once()) - ->method('canHold') - ->willReturn(false); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('No order(s) were put on hold.'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php deleted file mode 100644 index cddb503925987..0000000000000 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/MassUnholdTest.php +++ /dev/null @@ -1,252 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order; - -use Magento\Framework\App\Action\Context; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; - -/** - * Class MassHoldTest - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class MassUnholdTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Sales\Controller\Adminhtml\Order\MassUnhold - */ - protected $massAction; - - /** - * @var Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRedirectMock; - - /** - * @var \Magento\Framework\App\Request\Http|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; - - /** - * @var \Magento\Framework\Message\Manager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $messageManagerMock; - - /** - * @var \Magento\Framework\ObjectManager\ObjectManager|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Backend\Model\Session|\PHPUnit_Framework_MockObject_MockObject - */ - protected $sessionMock; - - /** - * @var \Magento\Framework\App\ActionFlag|\PHPUnit_Framework_MockObject_MockObject - */ - protected $actionFlagMock; - - /** - * @var \Magento\Backend\Helper\Data|\PHPUnit_Framework_MockObject_MockObject - */ - protected $helperMock; - - /** - * @var \Magento\Sales\Model\Order|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\Collection|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionMock; - - /** - * @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $orderCollectionFactoryMock; - - /** - * @var \Magento\Ui\Component\MassAction\Filter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $filterMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $orderManagementMock; - - protected function setUp() - { - $objectManagerHelper = new ObjectManagerHelper($this); - $this->contextMock = $this->createMock(\Magento\Backend\App\Action\Context::class); - $resultRedirectFactory = $this->createMock(\Magento\Backend\Model\View\Result\RedirectFactory::class); - $this->responseMock = $this->createMock(\Magento\Framework\App\ResponseInterface::class); - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class) - ->disableOriginalConstructor()->getMock(); - $this->objectManagerMock = $this->createMock(\Magento\Framework\ObjectManager\ObjectManager::class); - $this->messageManagerMock = $this->createMock(\Magento\Framework\Message\Manager::class); - - $this->orderCollectionMock = $this->getMockBuilder(\Magento\Sales\Model\ResourceModel\Order\Collection::class) - ->disableOriginalConstructor() - ->getMock(); - $orderCollection = \Magento\Sales\Model\ResourceModel\Order\CollectionFactory::class; - $this->orderCollectionFactoryMock = $this->getMockBuilder($orderCollection) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->sessionMock = $this->createPartialMock(\Magento\Backend\Model\Session::class, ['setIsUrlNotice']); - $this->actionFlagMock = $this->createPartialMock(\Magento\Framework\App\ActionFlag::class, ['get', 'set']); - $this->helperMock = $this->createPartialMock(\Magento\Backend\Helper\Data::class, ['getUrl']); - $this->resultRedirectMock = $this->createMock(\Magento\Backend\Model\View\Result\Redirect::class); - $resultRedirectFactory->expects($this->any())->method('create')->willReturn($this->resultRedirectMock); - - $redirectMock = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $resultFactoryMock->expects($this->any()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT) - ->willReturn($redirectMock); - - $this->contextMock->expects($this->once())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->once())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->once())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->once())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->once())->method('getSession')->willReturn($this->sessionMock); - $this->contextMock->expects($this->once())->method('getActionFlag')->willReturn($this->actionFlagMock); - $this->contextMock->expects($this->once())->method('getHelper')->willReturn($this->helperMock); - $this->contextMock - ->expects($this->once()) - ->method('getResultRedirectFactory') - ->willReturn($resultRedirectFactory); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($resultFactoryMock); - - $this->filterMock = $this->createMock(\Magento\Ui\Component\MassAction\Filter::class); - $this->filterMock->expects($this->once()) - ->method('getCollection') - ->with($this->orderCollectionMock) - ->willReturn($this->orderCollectionMock); - $this->orderCollectionFactoryMock->expects($this->once()) - ->method('create') - ->willReturn($this->orderCollectionMock); - - $this->orderManagementMock = $this->createMock(\Magento\Sales\Api\OrderManagementInterface::class); - - $this->massAction = $objectManagerHelper->getObject( - \Magento\Sales\Controller\Adminhtml\Order\MassUnhold::class, - [ - 'context' => $this->contextMock, - 'filter' => $this->filterMock, - 'collectionFactory' => $this->orderCollectionFactoryMock, - 'orderManagement' => $this->orderManagementMock - ] - ); - } - - public function testExecuteOneOrdersReleasedFromHold() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('canUnhold') - ->willReturn(true); - $order1->expects($this->once()) - ->method('getEntityId'); - - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn(count($orders)); - - $order2->expects($this->once()) - ->method('canUnhold') - ->willReturn(false); - - $this->orderManagementMock->expects($this->atLeastOnce())->method('unHold')->willReturn(true); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('1 order(s) were not released from on hold status.'); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with('1 order(s) have been released from on hold status.'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } - - public function testExecuteNoReleasedOrderFromHold() - { - $order1 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - $order2 = $this->getMockBuilder(\Magento\Sales\Model\Order::class) - ->disableOriginalConstructor() - ->getMock(); - - $orders = [$order1, $order2]; - - $this->orderCollectionMock->expects($this->any()) - ->method('getItems') - ->willReturn($orders); - - $order1->expects($this->once()) - ->method('canUnhold') - ->willReturn(false); - - $this->orderCollectionMock->expects($this->once()) - ->method('count') - ->willReturn(count($orders)); - - $order2->expects($this->once()) - ->method('canUnhold') - ->willReturn(false); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with('No order(s) were released from on hold status.'); - - $this->resultRedirectMock->expects($this->once()) - ->method('setPath') - ->with('sales/*/') - ->willReturnSelf(); - - $this->massAction->execute(); - } -} diff --git a/app/code/Magento/Sales/Test/Unit/Model/DownloadTest.php b/app/code/Magento/Sales/Test/Unit/Model/DownloadTest.php deleted file mode 100644 index dd430f8a03304..0000000000000 --- a/app/code/Magento/Sales/Test/Unit/Model/DownloadTest.php +++ /dev/null @@ -1,262 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Sales\Test\Unit\Model; - -use Magento\Framework\App\Filesystem\DirectoryList; - -class DownloadTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Sales\Model\Download - */ - protected $model; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $filesystemMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $storageMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $storageFactoryMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $httpFileFactoryMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $writeDirectoryMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - protected $driverMock; - - protected function setUp() - { - $this->writeDirectoryMock = $this->getMockBuilder(\Magento\Framework\Filesystem\Directory\Write::class) - ->disableOriginalConstructor() - ->getMock(); - $this->filesystemMock = $this->getMockBuilder(\Magento\Framework\Filesystem::class) - ->disableOriginalConstructor() - ->getMock(); - $this->filesystemMock->expects($this->any()) - ->method('getDirectoryWrite') - ->with(DirectoryList::MEDIA) - ->will($this->returnValue($this->writeDirectoryMock)); - - $this->driverMock = $this->getMockForAbstractClass(\Magento\Framework\Filesystem\DriverInterface::class); - $this->storageMock = $this->getMockBuilder(\Magento\MediaStorage\Helper\File\Storage\Database::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storageFactoryMock = $this->getMockBuilder( - \Magento\MediaStorage\Model\File\Storage\DatabaseFactory::class - )->disableOriginalConstructor() - ->setMethods(['create', 'checkDbUsage']) - ->getMock(); - $this->httpFileFactoryMock = $this->getMockBuilder(\Magento\Framework\App\Response\Http\FileFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->model = new \Magento\Sales\Model\Download( - $this->filesystemMock, - $this->storageMock, - $this->storageFactoryMock, - $this->httpFileFactoryMock - ); - } - - public function testInstanceOf() - { - $model = new \Magento\Sales\Model\Download( - $this->filesystemMock, - $this->storageMock, - $this->storageFactoryMock, - $this->httpFileFactoryMock - ); - $this->assertInstanceOf(\Magento\Sales\Model\Download::class, $model); - } - - /** - * @param $realPatchCheck - * @param $isFile - * @param $isReadable - * @expectedException \Magento\Framework\Exception\LocalizedException - * @dataProvider dataProviderForTestDownloadFileException - */ - public function testDownloadFileException($realPatchCheck, $isFile, $isReadable) - { - $info = ['order_path' => 'test/path', 'quote_path' => 'test/path2', 'title' => 'test title']; - - $this->writeDirectoryMock->expects($this->any()) - ->method('getAbsolutePath') - ->will($this->returnArgument(0)); - $this->writeDirectoryMock->expects($this->any()) - ->method('getDriver') - ->willReturn($this->driverMock); - $this->driverMock->expects($this->any())->method('getRealPath')->willReturn($realPatchCheck); - $this->writeDirectoryMock->expects($this->any()) - ->method('isFile') - ->will($this->returnValue($isFile)); - $this->writeDirectoryMock->expects($this->any()) - ->method('isReadable') - ->will($this->returnValue($isReadable)); - - $this->storageFactoryMock->expects($this->any()) - ->method('checkDbUsage') - ->will($this->returnValue(false)); - $this->httpFileFactoryMock->expects($this->never())->method('create'); - - $this->model->downloadFile($info); - } - - /** - * @return array - */ - public function dataProviderForTestDownloadFileException() - { - return [ - [1, true, false], - [1, false, true], - [false, true, true], - ]; - } - - /** - * @expectedException \Magento\Framework\Exception\LocalizedException - */ - public function testDownloadFileNoStorage() - { - $info = ['order_path' => 'test/path', 'quote_path' => 'test/path2', 'title' => 'test title']; - $isFile = true; - $isReadable = false; - - $this->writeDirectoryMock->expects($this->any()) - ->method('getAbsolutePath') - ->will($this->returnArgument(0)); - $this->writeDirectoryMock->expects($this->any()) - ->method('getDriver') - ->willReturn($this->driverMock); - $this->driverMock->expects($this->any())->method('getRealPath')->willReturn(true); - - $this->writeDirectoryMock->expects($this->any()) - ->method('isFile') - ->will($this->returnValue($isFile)); - $this->writeDirectoryMock->expects($this->any()) - ->method('isReadable') - ->will($this->returnValue($isReadable)); - - $this->storageMock->expects($this->any()) - ->method('checkDbUsage') - ->will($this->returnValue(true)); - - $storageDatabaseMock = $this->getMockBuilder(\Magento\MediaStorage\Model\File\Storage\Database::class) - ->disableOriginalConstructor() - ->getMock(); - $storageDatabaseMock->expects($this->at(0)) - ->method('loadByFilename') - ->with($this->equalTo($info['order_path'])) - ->will($this->returnSelf()); - $storageDatabaseMock->expects($this->at(2)) - ->method('loadByFilename') - ->with($this->equalTo($info['quote_path'])) - ->will($this->returnSelf()); - - $storageDatabaseMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue(false)); - - $this->storageFactoryMock->expects($this->any()) - ->method('create') - ->will($this->returnValue($storageDatabaseMock)); - $this->httpFileFactoryMock->expects($this->never())->method('create'); - - $this->model->downloadFile($info); - } - - public function testDownloadFile() - { - $info = ['order_path' => 'test/path', 'quote_path' => 'test/path2', 'title' => 'test title']; - $isFile = true; - $isReadable = false; - - $writeMock = $this->getMockBuilder(\Magento\Framework\Filesystem\File\Write::class) - ->disableOriginalConstructor() - ->getMock(); - $writeMock->expects($this->any()) - ->method('lock'); - $writeMock->expects($this->any()) - ->method('write'); - $writeMock->expects($this->any()) - ->method('unlock'); - $writeMock->expects($this->any()) - ->method('close'); - - $this->writeDirectoryMock->expects($this->any()) - ->method('getAbsolutePath') - ->will($this->returnArgument(0)); - $this->writeDirectoryMock->expects($this->any()) - ->method('getDriver') - ->willReturn($this->driverMock); - $this->driverMock->expects($this->any())->method('getRealPath')->willReturn(true); - - $this->writeDirectoryMock->expects($this->any()) - ->method('isFile') - ->will($this->returnValue($isFile)); - $this->writeDirectoryMock->expects($this->any()) - ->method('isReadable') - ->will($this->returnValue($isReadable)); - $this->writeDirectoryMock->expects($this->any()) - ->method('openFile') - ->will($this->returnValue($writeMock)); - $this->writeDirectoryMock->expects($this->once()) - ->method('getRelativePath') - ->with($info['order_path']) - ->will($this->returnArgument(0)); - - $this->storageMock->expects($this->any()) - ->method('checkDbUsage') - ->will($this->returnValue(true)); - - $storageDatabaseMock = $this->getMockBuilder(\Magento\MediaStorage\Model\File\Storage\Database::class) - ->disableOriginalConstructor() - ->setMethods(['loadByFilename', 'getId', '__wakeup']) - ->getMock(); - $storageDatabaseMock->expects($this->any()) - ->method('loadByFilename') - ->will($this->returnSelf()); - - $storageDatabaseMock->expects($this->any()) - ->method('getId') - ->will($this->returnValue(true)); - - $this->storageFactoryMock->expects($this->any()) - ->method('create') - ->will($this->returnValue($storageDatabaseMock)); - - $this->httpFileFactoryMock->expects($this->once()) - ->method('create') - ->with( - $info['title'], - ['value' => $info['order_path'], 'type' => 'filename'], - DirectoryList::MEDIA, - 'application/octet-stream', - null - ); - - $result = $this->model->downloadFile($info); - $this->assertNull($result); - } -} diff --git a/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php b/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php index 2e668f0b0d6f1..68681c6c5a66b 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/Service/CreditmemoServiceTest.php @@ -3,10 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Sales\Test\Unit\Model\Service; use Magento\Sales\Model\Order; +use Magento\Sales\Api\Data\CreditmemoInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + /** * Class CreditmemoServiceTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -14,34 +19,34 @@ class CreditmemoServiceTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Sales\Api\CreditmemoRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Sales\Api\CreditmemoRepositoryInterface|MockObject */ protected $creditmemoRepositoryMock; /** - * @var \Magento\Sales\Api\CreditmemoCommentRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Sales\Api\CreditmemoCommentRepositoryInterface|MockObject */ protected $creditmemoCommentRepositoryMock; /** - * @var \Magento\Framework\Api\SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Api\SearchCriteriaBuilder|MockObject */ protected $searchCriteriaBuilderMock; /** - * @var \Magento\Framework\Api\FilterBuilder|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Api\FilterBuilder|MockObject */ protected $filterBuilderMock; /** - * @var \Magento\Sales\Model\Order\CreditmemoNotifier|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Sales\Model\Order\CreditmemoNotifier|MockObject */ protected $creditmemoNotifierMock; /** - * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\PriceCurrencyInterface|MockObject */ - private $priceCurrencyMock; + private $priceCurrency; /** * @var \Magento\Sales\Model\Service\CreditmemoService @@ -79,7 +84,7 @@ protected function setUp() ['setField', 'setValue', 'setConditionType', 'create'] ); $this->creditmemoNotifierMock = $this->createMock(\Magento\Sales\Model\Order\CreditmemoNotifier::class); - $this->priceCurrencyMock = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) + $this->priceCurrency = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) ->getMockForAbstractClass(); $this->objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -91,7 +96,7 @@ protected function setUp() 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, 'filterBuilder' => $this->filterBuilderMock, 'creditmemoNotifier' => $this->creditmemoNotifierMock, - 'priceCurrency' => $this->priceCurrencyMock, + 'priceCurrency' => $this->priceCurrency, ] ); } @@ -187,7 +192,7 @@ public function testRefund() $orderMock->expects($this->once())->method('getBaseTotalPaid')->willReturn(10); $creditMemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn(10); - $this->priceCurrencyMock->expects($this->any()) + $this->priceCurrency->expects($this->any()) ->method('round') ->willReturnArgument(0); @@ -259,7 +264,7 @@ public function testRefundPendingCreditMemo() $orderMock->expects($this->once())->method('getBaseTotalPaid')->willReturn(10); $creditMemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn(10); - $this->priceCurrencyMock->expects($this->any()) + $this->priceCurrency->expects($this->any()) ->method('round') ->willReturnArgument(0); @@ -324,27 +329,32 @@ public function testRefundExpectsMoneyAvailableToReturn() $baseGrandTotal = 10; $baseTotalRefunded = 9; $baseTotalPaid = 10; - $creditMemoMock = $this->getMockBuilder(\Magento\Sales\Api\Data\CreditmemoInterface::class) - ->setMethods(['getId', 'getOrder', 'formatBasePrice']) + /** @var CreditmemoInterface|MockObject $creditMemo */ + $creditMemo = $this->getMockBuilder(CreditmemoInterface::class) + ->setMethods(['getId', 'getOrder']) ->getMockForAbstractClass(); - $creditMemoMock->expects($this->once())->method('getId')->willReturn(null); - $orderMock = $this->getMockBuilder(Order::class)->disableOriginalConstructor()->getMock(); - $creditMemoMock->expects($this->atLeastOnce())->method('getOrder')->willReturn($orderMock); - $creditMemoMock->expects($this->once())->method('getBaseGrandTotal')->willReturn($baseGrandTotal); - $orderMock->expects($this->atLeastOnce())->method('getBaseTotalRefunded')->willReturn($baseTotalRefunded); - $this->priceCurrencyMock->expects($this->exactly(2))->method('round')->withConsecutive( - [$baseTotalRefunded + $baseGrandTotal], - [$baseTotalPaid] - )->willReturnOnConsecutiveCalls( - $baseTotalRefunded + $baseGrandTotal, - $baseTotalPaid - ); - $orderMock->expects($this->atLeastOnce())->method('getBaseTotalPaid')->willReturn($baseTotalPaid); + $creditMemo->method('getId') + ->willReturn(null); + /** @var Order|MockObject $order */ + $order = $this->getMockBuilder(Order::class) + ->disableOriginalConstructor() + ->getMock(); + $creditMemo->method('getOrder') + ->willReturn($order); + $creditMemo->method('getBaseGrandTotal') + ->willReturn($baseGrandTotal); + $order->method('getBaseTotalRefunded') + ->willReturn($baseTotalRefunded); + $this->priceCurrency->method('round') + ->withConsecutive([$baseTotalRefunded + $baseGrandTotal], [$baseTotalPaid]) + ->willReturnOnConsecutiveCalls($baseTotalRefunded + $baseGrandTotal, $baseTotalPaid); + $order->method('getBaseTotalPaid') + ->willReturn($baseTotalPaid); $baseAvailableRefund = $baseTotalPaid - $baseTotalRefunded; - $orderMock->expects($this->once())->method('formatBasePrice')->with( - $baseAvailableRefund - )->willReturn($baseAvailableRefund); - $this->creditmemoService->refund($creditMemoMock, true); + $order->method('formatPriceTxt') + ->with($baseAvailableRefund) + ->willReturn($baseAvailableRefund); + $this->creditmemoService->refund($creditMemo, true); } /** diff --git a/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultMergerTest.php b/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultMergerTest.php new file mode 100644 index 0000000000000..ca2dd0a31b1e7 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultMergerTest.php @@ -0,0 +1,87 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Model; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Model\ValidatorResultInterface; +use Magento\Sales\Model\ValidatorResultInterfaceFactory; +use Magento\Sales\Model\ValidatorResultMerger; + +/** + * @covers \Magento\Sales\Model\ValidatorResultMerger + */ +class ValidatorResultMergerTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var ValidatorResultMerger + */ + private $validatorResultMerger; + + /** + * Object Manager + * + * @var ObjectManager + */ + private $objectManager; + + /** + * @var ValidatorResultInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $validatorResultFactoryMock; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->validatorResultFactoryMock = $this->getMockBuilder(ValidatorResultInterfaceFactory::class) + ->setMethods(['create'])->disableOriginalConstructor()->getMock(); + $this->objectManager = new ObjectManager($this); + $this->validatorResultMerger = $this->objectManager->getObject( + ValidatorResultMerger::class, + [ + 'validatorResultInterfaceFactory' => $this->validatorResultFactoryMock, + ] + ); + } + + /** + * Test merge method + * + * @return void + */ + public function testMerge() + { + $validatorResultMock = $this->createMock(ValidatorResultInterface::class); + $validationResult = $this->createMock(ValidatorResultInterface::class); + $cmValidationResult = $this->createMock(ValidatorResultInterface::class); + $validationMessages = [['test04', 'test05'], ['test06']]; + $this->validatorResultFactoryMock->expects($this->once())->method('create') + ->willReturn($validatorResultMock); + $validationResult->expects($this->once())->method('getMessages')->willReturn(['test01', 'test02']); + $cmValidationResult->expects($this->once())->method('getMessages')->willReturn(['test03']); + + $validatorResultMock->expects($this->at(0))->method('addMessage')->with('test01'); + $validatorResultMock->expects($this->at(1))->method('addMessage')->with('test02'); + $validatorResultMock->expects($this->at(2))->method('addMessage')->with('test03'); + $validatorResultMock->expects($this->at(3))->method('addMessage')->with('test04'); + $validatorResultMock->expects($this->at(4))->method('addMessage')->with('test05'); + $validatorResultMock->expects($this->at(5))->method('addMessage')->with('test06'); + $expected = $validatorResultMock; + $actual = $this->validatorResultMerger->merge( + $validationResult, + $cmValidationResult, + ...$validationMessages + ); + $this->assertEquals($expected, $actual); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultTest.php b/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultTest.php new file mode 100644 index 0000000000000..f4ab2d4f48e6f --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Model/ValidatorResultTest.php @@ -0,0 +1,75 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Model; + +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Model\ValidatorResult; + +/** + * @covers \Magento\Sales\Model\ValidatorResult + */ +class ValidatorResultTest extends \PHPUnit\Framework\TestCase +{ + /** + * Testable Object + * + * @var ValidatorResult + */ + private $validatorResult; + + /** + * Object Manager + * + * @var ObjectManager + */ + private $objectManager; + + /** + * Set Up + * + * @return void + */ + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + $this->validatorResult = $this->objectManager->getObject(ValidatorResult::class); + } + + /** + * Test addMessage method + * + * @return void + */ + public function testAddMessages() + { + $messageFirst = 'Sample message 01.'; + $messageSecond = 'Sample messages 02.'; + $messageThird = 'Sample messages 03.'; + $expected = [$messageFirst, $messageSecond, $messageThird]; + $this->validatorResult->addMessage($messageFirst); + $this->validatorResult->addMessage($messageSecond); + $this->validatorResult->addMessage($messageThird); + $actual = $this->validatorResult->getMessages(); + $this->assertEquals($expected, $actual); + } + + /** + * Test hasMessages method + * + * @return void + */ + public function testHasMessages() + { + $this->assertFalse($this->validatorResult->hasMessages()); + $messageFirst = 'Sample message 01.'; + $messageSecond = 'Sample messages 02.'; + $this->validatorResult->addMessage($messageFirst); + $this->validatorResult->addMessage($messageSecond); + $this->assertTrue($this->validatorResult->hasMessages()); + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php new file mode 100644 index 0000000000000..c6e02151b9bc1 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Observer/AssignOrderToCustomerObserverTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Observer; + +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\Event; +use Magento\Framework\Event\Observer; +use Magento\Sales\Api\Data\OrderInterface; +use Magento\Sales\Api\OrderRepositoryInterface; +use Magento\Sales\Observer\AssignOrderToCustomerObserver; +use PHPUnit\Framework\TestCase; +use PHPUnit_Framework_MockObject_MockObject; + +/** + * Class AssignOrderToCustomerObserverTest + */ +class AssignOrderToCustomerObserverTest extends TestCase +{ + /** @var AssignOrderToCustomerObserver */ + protected $sut; + + /** @var OrderRepositoryInterface|PHPUnit_Framework_MockObject_MockObject */ + protected $orderRepositoryMock; + + /** + * Set Up + */ + protected function setUp() + { + $this->orderRepositoryMock = $this->getMockBuilder(OrderRepositoryInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->sut = new AssignOrderToCustomerObserver($this->orderRepositoryMock); + } + + /** + * Test assigning order to customer after issuing guest order + * + * @dataProvider getCustomerIds + * @param null|int $customerId + * @return void + */ + public function testAssignOrderToCustomerAfterGuestOrder($customerId) + { + $orderId = 1; + /** @var Observer|PHPUnit_Framework_MockObject_MockObject $observerMock */ + $observerMock = $this->createMock(Observer::class); + /** @var Event|PHPUnit_Framework_MockObject_MockObject $eventMock */ + $eventMock = $this->getMockBuilder(Event::class)->disableOriginalConstructor() + ->setMethods(['getData']) + ->getMock(); + /** @var CustomerInterface|PHPUnit_Framework_MockObject_MockObject $customerMock */ + $customerMock = $this->createMock(CustomerInterface::class); + /** @var OrderInterface|PHPUnit_Framework_MockObject_MockObject $orderMock */ + $orderMock = $this->getMockBuilder(OrderInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $observerMock->expects($this->once())->method('getEvent')->willReturn($eventMock); + $eventMock->expects($this->any())->method('getData') + ->willReturnMap([ + ['delegate_data', null, ['__sales_assign_order_id' => $orderId]], + ['customer_data_object', null, $customerMock] + ]); + $orderMock->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $this->orderRepositoryMock->expects($this->once())->method('get')->with($orderId) + ->willReturn($orderMock); + if (!$customerId) { + $this->orderRepositoryMock->expects($this->once())->method('save')->with($orderMock); + $this->sut->execute($observerMock); + return ; + } + + $this->orderRepositoryMock->expects($this->never())->method('save')->with($orderMock); + $this->sut->execute($observerMock); + } + + /** + * Customer id assigned to order + * + * @return array + */ + public function getCustomerIds() + { + return [[null, 1]]; + } +} diff --git a/app/code/Magento/Sales/Test/Unit/Plugin/ShippingLabelConverterTest.php b/app/code/Magento/Sales/Test/Unit/Plugin/ShippingLabelConverterTest.php new file mode 100644 index 0000000000000..7ec15075c29c9 --- /dev/null +++ b/app/code/Magento/Sales/Test/Unit/Plugin/ShippingLabelConverterTest.php @@ -0,0 +1,113 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Test\Unit\Plugin; + +use Magento\Sales\Api\Data\ShipmentInterface; +use Magento\Sales\Api\Data\ShipmentSearchResultInterface; +use Magento\Sales\Api\ShipmentRepositoryInterface; +use Magento\Sales\Plugin\ShippingLabelConverter; + +/** + * Unit test for plugin to convert shipping label from blob to base64encoded string. + */ +class ShippingLabelConverterTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var ShippingLabelConverter + */ + private $model; + + /** + * @var ShipmentRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $shipmentRepositoryMock; + + /** + * @inheritdoc + */ + protected function setUp() + { + $this->model = new ShippingLabelConverter(); + $this->shipmentRepositoryMock = $this->getMockBuilder(ShipmentRepositoryInterface::class) + ->disableOriginalConstructor()->getMock(); + } + + /** + * @covers \Magento\Sales\Plugin\ShippingLabelConverter::afterGet() + * @param ShipmentInterface|\PHPUnit_Framework_MockObject_MockObject + * @return void + * @dataProvider shipmentDataProvider + */ + public function testAfterGet($shipmentMock) + { + $this->model->afterGet( + $this->shipmentRepositoryMock, + $shipmentMock + ); + } + + /** + * @covers \Magento\Sales\Plugin\ShippingLabelConverter::afterGetList() + * @param ShipmentInterface|\PHPUnit_Framework_MockObject_MockObject + * @return void + * @dataProvider shipmentDataProvider + */ + public function testAfterGetList($shipmentMock) + { + $searchResultMock = $this->getMockBuilder(ShipmentSearchResultInterface::class) + ->disableOriginalConstructor()->getMock(); + $searchResultMock->expects($this->once())->method('getItems')->willReturn([$shipmentMock]); + + $this->model->afterGetList( + $this->shipmentRepositoryMock, + $searchResultMock + ); + } + + /** + * @return array + */ + public function shipmentDataProvider() + { + return [ + ['shipmentMock' => $this->getShipmentMockWithLabel()], + ['shipmentMock' => $this->getShipmentMockWithOutLabel()], + ]; + } + + /** + * @return ShipmentInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function getShipmentMockWithLabel() + { + $shippingLabel = 'shipping_label_test'; + $shippingLabelEncoded = base64_encode('shipping_label_test'); + $shipmentMock = $this->getMockBuilder(ShipmentInterface::class) + ->disableOriginalConstructor()->getMock(); + $shipmentMock->expects($this->exactly(2))->method('getShippingLabel')->willReturn($shippingLabel); + $shipmentMock->expects($this->once()) + ->method('setShippingLabel') + ->with($shippingLabelEncoded) + ->willReturnSelf(); + + return $shipmentMock; + } + + /** + * @return ShipmentInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function getShipmentMockWithOutLabel() + { + $shipmentMock = $this->getMockBuilder(ShipmentInterface::class) + ->disableOriginalConstructor()->getMock(); + $shipmentMock->expects($this->once())->method('getShippingLabel')->willReturn(null); + $shipmentMock->expects($this->never())->method('setShippingLabel'); + + return $shipmentMock; + } +} diff --git a/app/code/Magento/Sales/composer.json b/app/code/Magento/Sales/composer.json index f3fda78f89dc1..a0e40d283da68 100644 --- a/app/code/Magento/Sales/composer.json +++ b/app/code/Magento/Sales/composer.json @@ -33,7 +33,7 @@ "magento/module-sales-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "101.0.4", + "version": "101.0.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Sales/etc/webapi_rest/di.xml b/app/code/Magento/Sales/etc/webapi_rest/di.xml index 0cfd36e219169..47fb3f188513c 100644 --- a/app/code/Magento/Sales/etc/webapi_rest/di.xml +++ b/app/code/Magento/Sales/etc/webapi_rest/di.xml @@ -12,4 +12,7 @@ <type name="Magento\Sales\Model\ResourceModel\Order"> <plugin name="authorization" type="Magento\Sales\Model\ResourceModel\Order\Plugin\Authorization" /> </type> + <type name="Magento\Sales\Api\ShipmentRepositoryInterface"> + <plugin name="convert_blob_to_string" type="Magento\Sales\Plugin\ShippingLabelConverter" /> + </type> </config> diff --git a/app/code/Magento/Sales/etc/webapi_soap/di.xml b/app/code/Magento/Sales/etc/webapi_soap/di.xml index 0cfd36e219169..47fb3f188513c 100644 --- a/app/code/Magento/Sales/etc/webapi_soap/di.xml +++ b/app/code/Magento/Sales/etc/webapi_soap/di.xml @@ -12,4 +12,7 @@ <type name="Magento\Sales\Model\ResourceModel\Order"> <plugin name="authorization" type="Magento\Sales\Model\ResourceModel\Order\Plugin\Authorization" /> </type> + <type name="Magento\Sales\Api\ShipmentRepositoryInterface"> + <plugin name="convert_blob_to_string" type="Magento\Sales\Plugin\ShippingLabelConverter" /> + </type> </config> 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 53ab6c204fa5b..259ca2165e647 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 @@ -48,7 +48,7 @@ define([ if (typeof controlButtonArea != 'undefined') { var buttons = controlButtonArea.childElements(); for (var i = 0; i < buttons.length; i++) { - if (buttons[i].innerHTML.include(button.label)) { + if (buttons[i].innerHTML.include(button.getLabel())) { return; } } @@ -921,6 +921,7 @@ define([ qtyElement.value = confirmedCurrentQty.value; } this.productConfigureAddFields['item['+itemId+'][configured]'] = 1; + this.itemsUpdate(); }.bind(this)); productConfigure.setShowWindowCallback(listType, function() { @@ -1170,8 +1171,12 @@ define([ submit : function() { - jQuery('#edit_form').trigger('processStart'); - jQuery('#edit_form').trigger('submitOrder'); + var $editForm = jQuery('#edit_form'); + + if ($editForm.valid()) { + $editForm.trigger('processStart'); + $editForm.trigger('submitOrder'); + } }, _realSubmit: function () { @@ -1431,6 +1436,10 @@ define([ node.update('<span>' + this._label + '</span>'); content[position] = node; Element.insert(element, content); + }, + + getLabel: function(){ + return this._label; } }; 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 50ffe979651ea..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,12 +12,21 @@ <body> <attribute name="class" value="account"/> <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="order_items" template="Magento_Sales::order/items.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"> <arguments> diff --git a/app/code/Magento/SalesAnalytics/Test/Mftf/composer.json b/app/code/Magento/SalesAnalytics/Test/Mftf/composer.json deleted file mode 100644 index c04b835e9cad9..0000000000000 --- a/app/code/Magento/SalesAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-sales-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-sales": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SalesAnalytics/composer.json b/app/code/Magento/SalesAnalytics/composer.json index dec1ae1d09ffe..242e2d811a718 100644 --- a/app/code/Magento/SalesAnalytics/composer.json +++ b/app/code/Magento/SalesAnalytics/composer.json @@ -7,7 +7,7 @@ "magento/module-sales": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/SalesInventory/Test/Mftf/composer.json b/app/code/Magento/SalesInventory/Test/Mftf/composer.json deleted file mode 100644 index cf4c0e7b96633..0000000000000 --- a/app/code/Magento/SalesInventory/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-sales-inventory", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SalesInventory/composer.json b/app/code/Magento/SalesInventory/composer.json index cc47daf9eb638..4dc2c7b9445b5 100644 --- a/app/code/Magento/SalesInventory/composer.json +++ b/app/code/Magento/SalesInventory/composer.json @@ -10,7 +10,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Widget/Chooser.php b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Widget/Chooser.php index b4be904223aac..6a54404e1865e 100644 --- a/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Widget/Chooser.php +++ b/app/code/Magento/SalesRule/Block/Adminhtml/Promo/Widget/Chooser.php @@ -87,7 +87,7 @@ public function prepareElementHtml(\Magento\Framework\Data\Form\Element\Abstract if ($element->getValue()) { $rule = $this->ruleFactory->create()->load((int)$element->getValue()); if ($rule->getId()) { - $chooser->setLabel($rule->getName()); + $chooser->setLabel($this->escapeHtml($rule->getName())); } } diff --git a/app/code/Magento/SalesRule/Model/Rule.php b/app/code/Magento/SalesRule/Model/Rule.php index 59efdf5eb3f6d..640398342bb5a 100644 --- a/app/code/Magento/SalesRule/Model/Rule.php +++ b/app/code/Magento/SalesRule/Model/Rule.php @@ -501,6 +501,8 @@ public function acquireCoupon($saveNewlyCreated = true, $saveAttemptCount = 10) $this->getUsesPerCustomer() ? $this->getUsesPerCustomer() : null )->setExpirationDate( $this->getToDate() + )->setType( + \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED ); $couponCode = self::getCouponCodeGenerator()->generateCode(); diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php index d2e7cabe473f4..c90894febd44b 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product.php @@ -25,6 +25,136 @@ protected function _addSpecialAttributes(array &$attributes) $attributes['quote_item_qty'] = __('Quantity in cart'); $attributes['quote_item_price'] = __('Price in cart'); $attributes['quote_item_row_total'] = __('Row total in cart'); + + $attributes['parent::category_ids'] = __('Category (Parent only)'); + $attributes['children::category_ids'] = __('Category (Children Only)'); + } + + /** + * Retrieve attribute + * + * @return string + */ + public function getAttribute(): string + { + $attribute = $this->getData('attribute'); + if (strpos($attribute, '::') !== false) { + list(, $attribute) = explode('::', $attribute); + } + + return $attribute; + } + + /** + * @inheritdoc + */ + public function getAttributeName() + { + $attribute = $this->getAttribute(); + if ($this->getAttributeScope()) { + $attribute = $this->getAttributeScope() . '::' . $attribute; + } + + return $this->getAttributeOption($attribute); + } + + /** + * @inheritdoc + */ + public function loadAttributeOptions() + { + $productAttributes = $this->_productResource->loadAllAttributes()->getAttributesByCode(); + + $attributes = []; + foreach ($productAttributes as $attribute) { + /* @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + if (!$attribute->isAllowedForRuleCondition() + || !$attribute->getDataUsingMethod($this->_isUsedForRuleProperty) + ) { + continue; + } + $frontLabel = $attribute->getFrontendLabel(); + $attributes[$attribute->getAttributeCode()] = $frontLabel; + $attributes['parent::' . $attribute->getAttributeCode()] = $frontLabel . __('(Parent Only)'); + $attributes['children::' . $attribute->getAttributeCode()] = $frontLabel . __('(Children Only)'); + } + + $this->_addSpecialAttributes($attributes); + + asort($attributes); + $this->setAttributeOption($attributes); + + return $this; + } + + /** + * @inheritdoc + */ + public function getAttributeElementHtml() + { + $html = parent::getAttributeElementHtml() . + $this->getAttributeScopeElement()->getHtml(); + + return $html; + } + + /** + * Retrieve form element for scope element + * + * @return \Magento\Framework\Data\Form\Element\AbstractElement + */ + private function getAttributeScopeElement(): \Magento\Framework\Data\Form\Element\AbstractElement + { + return $this->getForm()->addField( + $this->getPrefix() . '__' . $this->getId() . '__attribute_scope', + 'hidden', + [ + 'name' => $this->elementName . '[' . $this->getPrefix() . '][' . $this->getId() . '][attribute_scope]', + 'value' => $this->getAttributeScope(), + 'no_span' => true, + 'class' => 'hidden', + 'data-form-part' => $this->getFormName(), + ] + ); + } + + /** + * Set attribute value + * + * @param string $value + * @return void + */ + public function setAttribute(string $value) + { + if (strpos($value, '::') !== false) { + list($scope, $attribute) = explode('::', $value); + $this->setData('attribute_scope', $scope); + $this->setData('attribute', $attribute); + } else { + $this->setData('attribute', $value); + } + } + + /** + * @inheritdoc + */ + public function loadArray($arr) + { + parent::loadArray($arr); + $this->setAttributeScope($arr['attribute_scope'] ?? null); + + return $this; + } + + /** + * @inheritdoc + */ + public function asArray(array $arrAttributes = []) + { + $out = parent::asArray($arrAttributes); + $out['attribute_scope'] = $this->getAttributeScope(); + + return $out; } /** diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php index b5ac02e67b1e1..2277240eb8aad 100644 --- a/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php +++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Product/Combine.php @@ -8,6 +8,8 @@ use Magento\Catalog\Model\ResourceModel\Product\Collection; /** + * Combine conditions for product. + * * @api * @since 100.0.2 */ @@ -85,4 +87,76 @@ public function collectValidatedAttributes($productCollection) } return $this; } + + /** + * @inheritdoc + */ + protected function _isValid($entity) + { + if (!$this->getConditions()) { + return true; + } + + $all = $this->getAggregator() === 'all'; + $true = (bool)$this->getValue(); + + foreach ($this->getConditions() as $cond) { + if ($entity instanceof \Magento\Framework\Model\AbstractModel) { + $validated = $this->validateEntity($entity, $cond); + } else { + $validated = $cond->validateByEntityId($entity); + } + if ($all && $validated !== $true) { + return false; + } elseif (!$all && $validated === $true) { + return true; + } + } + + return $all ? true : false; + } + + /** + * Validate entity. + * + * @param \Magento\Framework\Model\AbstractModel $entity + * @param mixed $cond + * @return bool + */ + private function validateEntity(\Magento\Framework\Model\AbstractModel $entity, $cond): bool + { + $true = (bool)$this->getValue(); + $validated = !$true; + foreach ($this->retrieveValidateEntities($entity, $cond->getAttributeScope()) as $validateEntity) { + $validated = $cond->validate($validateEntity); + if ($validated === $true) { + break; + } + } + + return $validated; + } + + /** + * Retrieve entities for validation by attribute scope + * + * @param \Magento\Framework\Model\AbstractModel $entity + * @param string|null $attributeScope + * @return \Magento\Framework\Model\AbstractModel[] + */ + private function retrieveValidateEntities( + \Magento\Framework\Model\AbstractModel $entity, + $attributeScope + ): array { + if ($attributeScope === 'parent') { + $validateEntities = [$entity]; + } elseif ($attributeScope === 'children') { + $validateEntities = $entity->getChildren() ?: [$entity]; + } else { + $validateEntities = $entity->getChildren() ?: []; + $validateEntities[] = $entity; + } + + return $validateEntities; + } } diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml new file mode 100644 index 0000000000000..d3930a2767b0d --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/ApplyCartRuleOnStorefrontActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="ApplyCartRuleOnStorefrontActionGroup"> + <arguments> + <argument name="product"/> + <argument name="couponCode" type="string"/> + </arguments> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addToCart"/> + <waitForText userInput="You added {{product.name}} to your shopping cart." stepKey="waitForProductAddToShoppingCart"/> + <amOnPage url="{{CheckoutCartPage.url}}" stepKey="goToCheckoutPage"/> + <waitForPageLoad stepKey="waitForCheckoutPageIsOpened"/> + <click selector="{{AdminCartPriceRuleDiscountSection.discountTab}}" stepKey="clickToDiscountTab"/> + <fillField selector="{{AdminCartPriceRuleDiscountSection.couponInput}}" userInput="{{couponCode}}" + stepKey="fillCouponCode"/> + <click selector="{{AdminCartPriceRuleDiscountSection.applyCodeBtn}}" stepKey="applyCode"/> + <waitForPageLoad stepKey="waitForApplyCode"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml index d769e18c0cec3..35ad7f96bd18d 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml @@ -45,4 +45,41 @@ <data key="apply">Percent of product price discount</data> <data key="discountAmount">50</data> </entity> + <entity name="SalesRuleWithSkuInActions" type="SalesRule"> + <data key="name" unique="suffix">SimpleSalesRule</data> + <data key="websites">Main Website</data> + <data key="customerGroups">'NOT LOGGED IN'</data> + <data key="is_active">true</data> + <data key="coupon_type">Specific Coupon</data> + <data key="coupon_code">ABCD</data> + <data key="discount_amount">70</data> + </entity> + <entity name="SalesRuleSpecificCoupon" type="SalesRule"> + <data key="name" unique="suffix">SimpleSalesRule</data> + <data key="description">Sales Rule Descritpion</data> + <array key="website_ids"> + <item>1</item> + </array> + <array key="customer_group_ids"> + <item>0</item> + <item>1</item> + <item>3</item> + </array> + <data key="uses_per_customer">1</data> + <data key="is_active">true</data> + <data key="stop_rules_processing">false</data> + <data key="is_advanced">true</data> + <data key="sort_order">2</data> + <data key="simple_action">by_percent</data> + <data key="discount_amount">10</data> + <data key="discount_qty">1</data> + <data key="discount_step">0</data> + <data key="apply_to_shipping">false</data> + <data key="times_used">1</data> + <data key="is_rss">false</data> + <data key="coupon_type">SPECIFIC_COUPON</data> + <data key="use_auto_generation">true</data> + <data key="uses_per_coupon">2</data> + <data key="simple_free_shipping">1</data> + </entity> </entities> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-meta.xml b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-meta.xml index 1936f2b503517..0d4c4356a20a7 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-meta.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Metadata/sales_rule-meta.xml @@ -72,4 +72,7 @@ </object> </object> </operation> + <operation name="DeleteSalesRule" dataType="SalesRule" type="delete" auth="adminOauth" url="/V1/salesRules/{rule_id}" method="DELETE"> + <contentType>application/json</contentType> + </operation> </operations> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRulesPage.xml b/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRulesPage.xml index c96fb492925aa..6e4b2be8420b8 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRulesPage.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Page/AdminCartPriceRulesPage.xml @@ -10,5 +10,6 @@ <page name="AdminCartPriceRulesPage" url="sales_rule/promo_quote/" area="admin" module="Magento_SalesRule"> <section name="AdminCartPriceRulesSection"/> <section name="AdminCartPriceRulesFormSection"/> + <section name="AdminCartPriceRuleDiscountSection"/> </page> </pages> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Page/CheckoutCartPage.xml b/app/code/Magento/SalesRule/Test/Mftf/Page/CheckoutCartPage.xml new file mode 100644 index 0000000000000..83ec6095d57ee --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Page/CheckoutCartPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="CheckoutCartPage" url="/checkout/cart" area="storefront" module="Magento_Checkout"> + <section name="StorefrontDiscountSection"/> + </page> +</pages> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRuleDiscountSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRuleDiscountSection.xml new file mode 100644 index 0000000000000..d0aaa8e15f9ca --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRuleDiscountSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminCartPriceRuleDiscountSection"> + <element name="discountTab" type="button" selector="//strong[text()='Apply Discount Code']"/> + <element name="couponInput" type="input" selector="#coupon_code"/> + <element name="applyCodeBtn" type="button" selector="//span[text()='Apply Discount']"/> + </section> +</sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml index 86b97d6b724a6..14584acd03376 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml @@ -9,6 +9,7 @@ xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> <section name="AdminCartPriceRulesFormSection"> <element name="save" type="button" selector="#save" timeout="30"/> + <element name="saveAndContinue" type="button" selector="#save_and_continue" timeout="30"/> <element name="delete" type="button" selector="#delete" timeout="30"/> <element name="modalAcceptButton" type="button" selector="button.action-accept" timeout="30"/> @@ -16,6 +17,12 @@ <element name="ruleName" type="input" selector="input[name='name']"/> <element name="websites" type="multiselect" selector="select[name='website_ids']"/> <element name="customerGroups" type="multiselect" selector="select[name='customer_group_ids']"/> + <element name="coupon" type="select" selector="select[name='coupon_type']"/> + <element name="couponCode" type="input" selector="input[name='coupon_code']"/> + <element name="useAutoGeneration" type="checkbox" selector="input[name='use_auto_generation']"/> + <element name="userPerCoupon" type="input" selector="//input[@name='uses_per_coupon']"/> + <element name="userPerCustomer" type="input" selector="//input[@name='uses_per_customer']"/> + <element name="priority" type="input" selector="//*[@name='sort_order']"/> <!-- Actions sub-form --> <element name="actionsHeader" type="button" selector="div[data-index='actions']" timeout="30"/> @@ -23,5 +30,19 @@ <element name="applyDiscountToShipping" type="checkbox" selector="input[name='apply_to_shipping']"/> <element name="applyDiscountToShippingLabel" type="checkbox" selector="input[name='apply_to_shipping']+label"/> <element name="discountAmount" type="input" selector="input[name='discount_amount']"/> + <element name="discountStep" type="input" selector="input[name='discount_step']"/> + <element name="freeShipping" type="select" selector="select[name='simple_free_shipping']"/> + <element name="conditions" type="button" selector=".rule-param.rule-param-new-child > a"/> + <element name="condition" type="text" selector="//span[@class='rule-param']/a[text()='{{arg}}']" parameterized="true"/> + <element name="operator" type="select" selector="select[name*='[operator]']"/> + <element name="childAttribute" type="select" selector="select[name*='new_child']"/> + <element name="optionInput" type="input" selector="ul[class*='rule-param-children'] input[name*='[value]']"/> + + <!-- Manage Coupon Codes sub-form --> + <element name="manageCouponCodesHeader" type="button" selector="div[data-index='manage_coupon_codes']" timeout="30"/> + <element name="successMessage" type="text" selector="div.message.message-success.success"/> + <element name="couponQty" type="input" selector="#coupons_qty"/> + <element name="generateCouponsButton" type="button" selector="#coupons_generate_button" timeout="30"/> + <element name="generatedCouponByIndex" type="text" selector="#couponCodesGrid_table > tbody > tr:nth-child({{var}}) > td.col-code" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml index 8e55f419c49aa..a341c4a3bf769 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesSection.xml @@ -13,5 +13,7 @@ <element name="filterByNameInput" type="input" selector="input[name='name']"/> <element name="searchButton" type="button" selector="#promo_quote_grid button[title='Search']" timeout="30"/> <element name="rowByIndex" type="text" selector="tr[data-role='row']:nth-of-type({{var1}})" parameterized="true" timeout="30"/> + <element name="nameColumns" type="text" selector="td[data-column='name']"/> + <element name="rowContainingText" type="text" selector="//*[@id='promo_quote_grid_table']/tbody/tr[td//text()[contains(., '{{var1}}')]]" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontDiscountSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontDiscountSection.xml new file mode 100644 index 0000000000000..2ae50489b6d12 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/StorefrontDiscountSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="StorefrontDiscountSection"> + <element name="discountTab" type="button" selector="#block-discount"/> + <element name="couponInput" type="input" selector="#coupon_code"/> + <element name="applyCodeBtn" type="button" selector="//span[text()='Apply Discount']"/> + </section> +</sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml new file mode 100644 index 0000000000000..f7010822f5f43 --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForConfigurableProductTest.xml @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="CartPriceRuleForConfigurableProductTest"> + <annotations> + <features value="SalesRule"/> + <stories value="MAGETWO-86098 - Cart Price Rule for configurable products"/> + <title value="Checking Cart Price Rule for configurable products"/> + <description value="Checking Cart Price Rule for configurable products"/> + <severity value="BLOCKER"/> + <testCaseId value="MAGETWO-95121"/> + <group value="sales_rule"/> + </annotations> + + <before> + <!--Login to Admin--> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <!-- Create the category --> + <createData entity="ApiCategory" stepKey="createCategory"/> + <!-- Create the configurable product and add it to the category --> + <createData entity="ApiConfigurableProduct" stepKey="createConfigProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <!-- Create an attribute with two options to be used in the first child product --> + <createData entity="productAttributeWithDropdownTwoOptions" stepKey="createConfigProductAttribute"/> + <createData entity="productAttributeOption1" stepKey="createConfigProductAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <createData entity="productAttributeOption2" stepKey="createConfigProductAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <!-- Add the attribute we just created to default attribute set --> + <createData entity="AddToDefaultSet" stepKey="createConfigAddToAttributeSet"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </createData> + <!-- Get the option of the attribute we created --> + <getData entity="ProductAttributeOptionGetter" index="1" stepKey="getConfigAttributeOption1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <getData entity="ProductAttributeOptionGetter" index="2" stepKey="getConfigAttributeOption2"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + </getData> + <!-- Create a simple product and give it the attribute with option --> + <createData entity="SimpleOption" stepKey="createConfigChildProduct1"> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + </createData> + <createData entity="SimpleOption" stepKey="createConfigChildProduct2"> + <field key="sku">SimpleTwoOption</field> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <!-- Create the configurable product --> + <createData entity="ConfigurableProductTwoOptions" stepKey="createConfigProductOption"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigProductAttribute"/> + <requiredEntity createDataKey="getConfigAttributeOption1"/> + <requiredEntity createDataKey="getConfigAttributeOption2"/> + </createData> + <!-- Add simple product to the configurable product --> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild1"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct1"/> + </createData> + <createData entity="ConfigurableProductAddChild" stepKey="createConfigProductAddChild2"> + <requiredEntity createDataKey="createConfigProduct"/> + <requiredEntity createDataKey="createConfigChildProduct2"/> + </createData> + + <!--Set attribute sku property Use for Promo Rule Conditions = Yes and save attribute--> + <actionGroup ref="navigateToProductAttribute" stepKey="goToProductAttributeSkuPage"> + <argument name="attributeCode" value="sku"/> + </actionGroup> + <click selector="{{AdminEditAttributeStorefrontPropertiesSection.storeFrontPropertiesTab}}" stepKey="clickStorefrontPropertiesTab"/> + <scrollTo selector="{{AdminEditAttributeStorefrontPropertiesSection.storeFrontPropertiesTab}}" stepKey="scrollToUseForPromoRuleConditions"/> + <selectOption selector="{{AdminEditAttributeStorefrontPropertiesSection.useForPromoRuleConditions}}" userInput="Yes" stepKey="changeAttributeProperty"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="saveAttribute"/> + <waitForPageLoad stepKey="waitForAttributeIsSaved"/> + + <!-- Create cart price rule --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SalesRuleWithSkuInActions.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="{{SalesRuleWithSkuInActions.websites}}" stepKey="selectWebsites"/> + <actionGroup ref="selectNotLoggedInCustomerGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.coupon}}" userInput="{{SalesRuleWithSkuInActions.coupon_type}}" stepKey="selectCouponType"/> + <fillField selector="{{AdminCartPriceRulesFormSection.couponCode}}" userInput="{{SalesRuleWithSkuInActions.coupon_code}}" stepKey="fillCouponCOde"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="{{SalesRuleWithSkuInActions.discount_amount}}" stepKey="fillDiscountAmount"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditions}}" stepKey="scrollToApplyRuleForConditions"/> + <click selector="{{AdminCartPriceRulesFormSection.conditions}}" stepKey="addConditionForRule"/> + <waitForPageLoad stepKey="waitForDropDownOpened"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.childAttribute}}" userInput="SKU(Children Only)" stepKey="selectAttribute"/> + <waitForPageLoad stepKey="waitForOperatorOpened"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('is')}}" stepKey="clickToChooseCondition"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.operator}}" userInput="is not one of" stepKey="selectOperator"/> + <waitForPageLoad stepKey="waitForOperatorOpened1"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('...')}}" stepKey="clickToChooseOption"/> + <waitForPageLoad stepKey="waitForConditionOpened2"/> + <fillField selector="{{AdminCartPriceRulesFormSection.optionInput}}" userInput="$$createConfigChildProduct1.sku$$" stepKey="selectOption"/> + <waitForPageLoad stepKey="waitForPageLoaded"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + </before> + + <after> + <!--Remove SalesRule--> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="deleteSalesRule"> + <argument name="ruleName" value="{{SalesRuleWithSkuInActions.name}}"/> + </actionGroup> + <!--Return default value to attribute sku--> + <actionGroup ref="navigateToProductAttribute" stepKey="goToProductAttributeSkuPage"> + <argument name="attributeCode" value="sku"/> + </actionGroup> + <click selector="{{AdminEditAttributeStorefrontPropertiesSection.storeFrontPropertiesTab}}" stepKey="clickStorefrontPropertiesTab1"/> + <scrollTo selector="{{AdminEditAttributeStorefrontPropertiesSection.storeFrontPropertiesTab}}" stepKey="scrollToUseForPromoRuleConditions"/> + <selectOption selector="{{AdminEditAttributeStorefrontPropertiesSection.useForPromoRuleConditions}}" userInput="No" stepKey="changeAttributeProperty"/> + <click selector="{{AdminMainActionsSection.save}}" stepKey="saveAttribute"/> + <waitForPageLoad stepKey="waitForAttributeIsSaved"/> + + <!--Remove configurable product and it's children--> + <deleteData createDataKey="createConfigProduct" stepKey="deleteConfigProduct"/> + <deleteData createDataKey="createConfigChildProduct1" stepKey="deleteConfigChildProduct1"/> + <deleteData createDataKey="createConfigChildProduct2" stepKey="deleteConfigChildProduct2"/> + <deleteData createDataKey="createConfigProductAttribute" stepKey="deleteConfigProductAttribute"/> + <deleteData createDataKey="createCategory" stepKey="deleteApiCategory"/> + <!--Logout from Admin--> + <actionGroup ref="logout" stepKey="logout"/> + </after> + + <!--Add the configureble product with first option to the cart --> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct.sku$$)}}" stepKey="goToProductPage"/> + <waitForPageLoad stepKey="waitForProductPageLoad"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" + userInput="$$getConfigAttributeOption1.label$$" stepKey="selectOption1"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart"/> + <waitForPageLoad stepKey="waitForAddToCart"/> + <!--Add the configureble product with second option to the cart --> + <amOnPage url="{{StorefrontProductPage.url($$createConfigProduct.sku$$)}}" stepKey="goToProductPage1"/> + <waitForPageLoad stepKey="waitForProductPageLoad1"/> + <selectOption selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" + userInput="$$getConfigAttributeOption2.label$$" stepKey="selectOption2"/> + <click selector="{{StorefrontProductActionSection.addToCart}}" stepKey="addProductToCart1"/> + <waitForPageLoad stepKey="waitForAddToCart1"/> + + <!--View and edit cart--> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="storefrontOpenCartFromMinicart"/> + <scrollTo selector="{{StorefrontDiscountSection.discountTab}}" stepKey="scrollToDiscountTab"/> + <click selector="{{StorefrontDiscountSection.discountTab}}" stepKey="openDiscountTab" /> + <fillField selector="{{StorefrontDiscountSection.couponInput}}" userInput="{{SalesRuleWithSkuInActions.coupon_code}}" stepKey="fillCouponCode" /> + <click selector="{{StorefrontDiscountSection.applyCodeBtn}}" stepKey="applyCode"/> + <waitForPageLoad stepKey="waitForPageLoaded1"/> + <see userInput="You used coupon code" stepKey="assertText"/> + <!--Verify values--> + <grabTextFrom selector="{{StorefrontCheckoutCartSummarySection.itemDiscount}}" stepKey="getDiscount"/> + <grabTextFrom selector="{{StorefrontCheckoutCartSummarySection.subtotal}}" stepKey="getSubtotal"/> + <assertEquals stepKey="checkDescount"> + <expectedResult type="string">-$7.00</expectedResult> + <actualResult type="variable">$getDiscount</actualResult> + </assertEquals> + <assertEquals stepKey="checkSubtotal"> + <expectedResult type="string">$20.00</expectedResult> + <actualResult type="variable">$getSubtotal</actualResult> + </assertEquals> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml new file mode 100644 index 0000000000000..4a5ae1b03b91c --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml @@ -0,0 +1,162 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontAutoGeneratedCouponCodeTest"> + <annotations> + <features value="SalesRule"/> + <stories value="Create cart price rule"/> + <title value="[Cart Price Rule] Auto generated coupon code considers 'Uses per Coupon' and 'Uses per Customer' options"/> + <description + value="[Cart Price Rule] Auto generated coupon code considers 'Uses per Coupon' and 'Uses per Customer' options"/> + <severity value="AVERAGE"/> + <testCaseId value="MAGETWO-77724"/> + <group value="salesRule"/> + </annotations> + + <before> + <!-- Create customer --> + <createData entity="Simple_US_Customer" stepKey="createCustomer"/> + <!-- Create simple product--> + <createData entity="SimpleProduct3" stepKey="createSimpleProduct"/> + <!-- Create a cart price rule --> + <createData entity="SalesRuleSpecificCoupon" stepKey="createSalesRule"/> + </before> + + <after> + <!-- Delete the cart price rule we made during the test --> + <deleteData createDataKey="createSalesRule" stepKey="deleteSalesRule"/> + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Login as Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- Search Cart Price Rule and go to edit Cart Price Rule --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForCartPriceRulesIndexPageIsOpened"/> + <fillField selector="{{AdminCartPriceRulesSection.filterByNameInput}}" userInput="$$createSalesRule.name$$" + stepKey="fillFieldFilterByName"/> + <click selector="{{AdminCartPriceRulesSection.searchButton}}" stepKey="clickSearchButton"/> + <see selector="{{AdminCartPriceRulesSection.nameColumns}}" userInput="$$createSalesRule.name$$" + stepKey="seeRuleName"/> + <click selector="{{AdminCartPriceRulesSection.rowContainingText($$createSalesRule.name$$)}}" + stepKey="goToEditRule"/> + + <!-- Step 3-4. Navigate to Manage Coupon Codes section to generate 1 coupon code --> + <conditionalClick selector="{{AdminCartPriceRulesFormSection.manageCouponCodesHeader}}" + dependentSelector="{{AdminCartPriceRulesFormSection.manageCouponCodesHeader}}" visible="true" + stepKey="clickManageCouponCodes"/> + <fillField selector="{{AdminCartPriceRulesFormSection.couponQty}}" userInput="1" stepKey="fillFieldCouponQty"/> + <click selector="{{AdminCartPriceRulesFormSection.generateCouponsButton}}" stepKey="clickGenerateCoupon"/> + <see selector="{{AdminCartPriceRulesFormSection.successMessage}}" userInput="1 coupon(s) have been generated." + stepKey="seeSuccessMessage"/> + <grabTextFrom selector="{{AdminCartPriceRulesFormSection.generatedCouponByIndex('1')}}" + stepKey="couponCode"/> + + <!-- Step: 5. Login to storefront as previously created customer --> + <actionGroup ref="CustomerLoginOnStorefront" stepKey="loginAsCustomer"> + <argument name="customer" value="$$createCustomer$$"/> + </actionGroup> + + <!-- Step: 6-7. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage"/> + <waitForPageLoad time="30" stepKey="waitForProductPageIsOpened"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='You used coupon code "{$couponCode}"' stepKey="waitForSuccessMessage"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput='You used coupon code "{$couponCode}"' + stepKey="seeSuccessMessage1"/> + <waitForElementVisible selector="{{StorefrontCheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementDiscountVisible"/> + + <!-- Step 8. Go to Checkout and Click Place Order button --> + <click selector="{{StorefrontCheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <click selector="{{CheckoutShippingMethodsSection.checkShippingMethodByName('Flat Rate')}}" + stepKey="selectFlatShippingMethod"/> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> + <waitForElement selector="{{CheckoutShippingMethodsSection.next}}" time="30" stepKey="waitForNextButton"/> + <click selector="{{CheckoutShippingMethodsSection.next}}" stepKey="clickNext"/> + <waitForElement selector="{{CheckoutPaymentSection.placeOrder}}" time="30" + stepKey="waitForPaymentSectionLoaded"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder"/> + <waitForElement selector="{{CheckoutSuccessMainSection.success}}" time="30" stepKey="waitForLoadSuccessPage"/> + + <!-- Step: 9-10. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage1"/> + <waitForPageLoad time="30" stepKey="waitForProductPageIsOpened1"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule1"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='The coupon code "{$couponCode}" is not valid.' stepKey="waitForErrorMessage"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput='The coupon code "{$couponCode}" is not valid.' + stepKey="seeErrorMessages"/> + <waitForElementNotVisible selector="{{StorefrontCheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementNotDiscountVisible"/> + + <!-- Step 11. Log out from storefront --> + <amOnPage url="{{StorefrontCustomerSignOutPage.url}}" stepKey="storefrontSignOut"/> + <waitForLoadingMaskToDisappear stepKey="waitSignOutPage"/> + + <!-- Step: 12-13. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage2"/> + <waitForPageLoad time="30" stepKey="waitForProductPageIsOpened2"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule2"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='You used coupon code "{$couponCode}"' stepKey="waitForSuccessMessage1"/> + <see selector="{{StorefrontMessagesSection.success}}" userInput='You used coupon code "{$couponCode}"' + stepKey="seeSuccessMessage2"/> + <waitForElementVisible selector="{{StorefrontCheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementDiscountVisible1"/> + + <!-- Step 14. Go to Checkout and Click Place Order button --> + <click selector="{{StorefrontCheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout1"/> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShippingSection"> + <argument name="customerVar" value="CustomerEntityOne"/> + <argument name="customerAddressVar" value="CustomerAddressSimple"/> + </actionGroup> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrder1"/> + <waitForElement selector="{{CheckoutSuccessMainSection.success}}" time="30" stepKey="waitForLoadSuccessPage1"/> + + <!-- Step: 15-16. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage3"/> + <waitForPageLoad time="30" stepKey="waitForProductPageIsOpened3"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule3"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='The coupon code "{$couponCode}" is not valid.' stepKey="waitForErrorMessage1"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput='The coupon code "{$couponCode}" is not valid.' + stepKey="seeErrorMessages1"/> + <waitForElementNotVisible selector="{{StorefrontCheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementNotDiscountVisible1"/> + + <!-- Step: 17. Reset Cookie --> + <resetCookie userInput="PHPSESSID" stepKey="resetCookie"/> + + <!-- Step: 18-19. Open the Product Page, add the product to Cart, go to Shopping Cart and Apply the same coupon code --> + <amOnPage url="{{StorefrontProductPage.url($$createSimpleProduct.name$$)}}" stepKey="openProductPage4"/> + <waitForPageLoad time="30" stepKey="waitForProductPageIsOpened4"/> + <actionGroup ref="ApplyCartRuleOnStorefrontActionGroup" stepKey="applyCartPriceRule4"> + <argument name="product" value="$$createSimpleProduct$$"/> + <argument name="couponCode" value="{$couponCode}"/> + </actionGroup> + <waitForText userInput='The coupon code "{$couponCode}" is not valid.' stepKey="waitForErrorMessage2"/> + <see selector="{{StorefrontMessagesSection.error}}" userInput='The coupon code "{$couponCode}" is not valid.' + stepKey="seeErrorMessages2"/> + <waitForElementNotVisible selector="{{StorefrontCheckoutCartSummarySection.discountAmount}}" time="30" + stepKey="waitForElementNotDiscountVisible2"/> + </test> +</tests> diff --git a/app/code/Magento/SalesRule/Test/Mftf/composer.json b/app/code/Magento/SalesRule/Test/Mftf/composer.json deleted file mode 100644 index 5d213301c45d8..0000000000000 --- a/app/code/Magento/SalesRule/Test/Mftf/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "magento/functional-test-module-sales-rule", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-rule": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-catalog-rule": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-sales-rule-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php index b217c4bdf8c8b..929cdc1e1ec7e 100644 --- a/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php +++ b/app/code/Magento/SalesRule/Test/Unit/Model/Rule/Condition/ProductTest.php @@ -295,8 +295,8 @@ public function testQuoteLocaleFormatPrice($isValid, $conditionValue, $operator ->method('getProduct') ->willReturn($product); - $this->model->setAttribute('quote_item_price') - ->setOperator($operator); + $this->model->setAttribute('quote_item_price'); + $this->model->setData('operator', $operator); $this->assertEquals($isValid, $this->model->setValue($conditionValue)->validate($item)); } diff --git a/app/code/Magento/SalesRule/composer.json b/app/code/Magento/SalesRule/composer.json index d5e1c908a1b52..3751aca28aef2 100644 --- a/app/code/Magento/SalesRule/composer.json +++ b/app/code/Magento/SalesRule/composer.json @@ -25,7 +25,7 @@ "magento/module-sales-rule-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "101.0.3", + "version": "101.0.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/SalesSequence/Test/Mftf/composer.json b/app/code/Magento/SalesSequence/Test/Mftf/composer.json deleted file mode 100644 index 2e70bc022fe26..0000000000000 --- a/app/code/Magento/SalesSequence/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-sales-sequence", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SalesSequence/composer.json b/app/code/Magento/SalesSequence/composer.json index e0cb249340961..8c4993966d4bb 100644 --- a/app/code/Magento/SalesSequence/composer.json +++ b/app/code/Magento/SalesSequence/composer.json @@ -6,7 +6,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/SampleData/Test/Mftf/composer.json b/app/code/Magento/SampleData/Test/Mftf/composer.json deleted file mode 100644 index 8d77f890dd31a..0000000000000 --- a/app/code/Magento/SampleData/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-sample-data", - "description": "Sample Data fixtures", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/sample-data-media": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SampleData/composer.json b/app/code/Magento/SampleData/composer.json index ed0823367613f..7d9e64740f240 100644 --- a/app/code/Magento/SampleData/composer.json +++ b/app/code/Magento/SampleData/composer.json @@ -9,7 +9,7 @@ "magento/sample-data-media": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Search/Test/Mftf/composer.json b/app/code/Magento/Search/Test/Mftf/composer.json deleted file mode 100644 index 1d2212b5cff95..0000000000000 --- a/app/code/Magento/Search/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-search", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog-search": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Search/composer.json b/app/code/Magento/Search/composer.json index 5bafa7bc96bbe..2b474fdba93dc 100644 --- a/app/code/Magento/Search/composer.json +++ b/app/code/Magento/Search/composer.json @@ -11,7 +11,7 @@ "magento/module-ui": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Search/view/frontend/web/js/form-mini.js b/app/code/Magento/Search/view/frontend/web/js/form-mini.js index 86d430041d7a8..15bcf2e73393e 100644 --- a/app/code/Magento/Search/view/frontend/web/js/form-mini.js +++ b/app/code/Magento/Search/view/frontend/web/js/form-mini.js @@ -306,12 +306,13 @@ define([ dropdown.append(html); }); + this._resetResponseList(true); + this.responseList.indexList = this.autoComplete.html(dropdown) .css(clonePosition) .show() .find(this.options.responseFieldElements + ':visible'); - this._resetResponseList(false); this.element.removeAttr('aria-activedescendant'); if (this.responseList.indexList.length) { @@ -338,6 +339,11 @@ define([ this._resetResponseList(false); } }.bind(this)); + } else { + this._resetResponseList(true); + this.autoComplete.hide(); + this._updateAriaHasPopup(false); + this.element.removeAttr('aria-activedescendant'); } }, this)); } else { diff --git a/app/code/Magento/Security/Test/Mftf/composer.json b/app/code/Magento/Security/Test/Mftf/composer.json deleted file mode 100644 index 8c945d48262c1..0000000000000 --- a/app/code/Magento/Security/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-security", - "description": "Security management module", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-customer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Security/Test/Unit/Model/SecurityCookieTest.php b/app/code/Magento/Security/Test/Unit/Model/SecurityCookieTest.php index b310bf63bc989..3a1855b3a220f 100644 --- a/app/code/Magento/Security/Test/Unit/Model/SecurityCookieTest.php +++ b/app/code/Magento/Security/Test/Unit/Model/SecurityCookieTest.php @@ -87,7 +87,7 @@ public function testGetLogoutReasonCookie() ) ->willReturn($cookie); - $this->assertEquals(intval($cookie), $this->model->getLogoutReasonCookie()); + $this->assertEquals((int)$cookie, $this->model->getLogoutReasonCookie()); } /** @@ -114,7 +114,7 @@ public function testSetLogoutReasonCookie() ->method('setPublicCookie') ->with( SecurityCookie::LOGOUT_REASON_CODE_COOKIE_NAME, - intval($status), + (int)$status, $this->cookieMetadataMock ) ->willReturnSelf(); diff --git a/app/code/Magento/Security/composer.json b/app/code/Magento/Security/composer.json index 0ef75d3e2bb8f..5422b6d606551 100644 --- a/app/code/Magento/Security/composer.json +++ b/app/code/Magento/Security/composer.json @@ -11,7 +11,7 @@ "magento/module-customer": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/SendFriend/Test/Mftf/composer.json b/app/code/Magento/SendFriend/Test/Mftf/composer.json deleted file mode 100644 index 596f0e03f8bfe..0000000000000 --- a/app/code/Magento/SendFriend/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-send-friend", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SendFriend/composer.json b/app/code/Magento/SendFriend/composer.json index df9bdf023a0e9..324465885f82f 100644 --- a/app/code/Magento/SendFriend/composer.json +++ b/app/code/Magento/SendFriend/composer.json @@ -9,7 +9,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml index 2b25e0efab84a..4922a9f365ced 100644 --- a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml +++ b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml @@ -17,13 +17,13 @@ <div class="secondary"> <button type="button" id="btn-remove<%- data._index_ %>" class="action remove" title="<?= $block->escapeHtmlAttr(__('Remove Recipent')) ?>"> - <span><?= $block->escapeJs($block->escapeHtml(__('Remove'))) ?></span> + <span><?= $block->escapeHtml(__('Remove')) ?></span> </button> </div> </div> <fieldset class="fieldset"> <div class="field name required"> - <label for="recipients-name<%- data._index_ %>" class="label"><span><?= $block->escapeJs($block->escapeHtml(__('Name'))) ?></span></label> + <label for="recipients-name<%- data._index_ %>" class="label"><span><?= $block->escapeHtml(__('Name')) ?></span></label> <div class="control"> <input name="recipients[name][<%- data._index_ %>]" type="text" title="<?= $block->escapeHtmlAttr(__('Name')) ?>" class="input-text" id="recipients-name<%- data._index_ %>" data-validate="{required:true}"/> @@ -31,7 +31,7 @@ </div> <div class="field email required"> - <label for="recipients-email<%- data._index_ %>" class="label"><span><?= $block->escapeJs($block->escapeHtml(__('Email'))) ?></span></label> + <label for="recipients-email<%- data._index_ %>" class="label"><span><?= $block->escapeHtml(__('Email')) ?></span></label> <div class="control"> <input name="recipients[email][<%- data._index_ %>]" title="<?= $block->escapeHtmlAttr(__('Email')) ?>" id="recipients-email<%- data._index_ %>" type="email" class="input-text" diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml index 13bcb2c9373bf..c0bd1bf8d0451 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/FlatRateShippingMethodData.xml @@ -16,7 +16,13 @@ <entity name="flatRateActiveEnable" type="active"> <data key="value">1</data> </entity> - + <!-- Disable Flat Rate Shipping method config --> + <entity name="DisableFlatRateShippingMethodConfig" type="flat_rate_shipping_method"> + <requiredEntity type="active">flatRateActiveDisable</requiredEntity> + </entity> + <entity name="flatRateActiveDisable" type="active"> + <data key="value">0</data> + </entity> <!-- Flat Rate Shipping method default setup --> <entity name="FlatRateShippingMethodDefault" type="flat_rate_shipping_method"> <requiredEntity type="active">flatRateActiveDefault</requiredEntity> diff --git a/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml b/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml index c3a24043da88c..a6f01cd500322 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Data/FreeShippingMethodData.xml @@ -57,4 +57,19 @@ <entity name="freeSortOrderDefault" type="sort_order"> <data key="value" /> </entity> + + <!--Set Free Shipping Subtotal to 101--> + <entity name="setFreeShippingSubtotal" type="free_shipping_method"> + <requiredEntity type="free_shipping_subtotal">freeShippingSubtotal</requiredEntity> + </entity> + <entity name="freeShippingSubtotal" type="free_shipping_subtotal"> + <data key="value">101</data> + </entity> + <!--Set to default Free Shipping Subtotal--> + <entity name="setFreeShippingSubtotalToDefault" type="free_shipping_method"> + <requiredEntity type="free_shipping_subtotal">freeShippingSubtotalDefault</requiredEntity> + </entity> + <entity name="freeShippingSubtotalDefault" type="free_shipping_subtotal"> + <data key="value">0</data> + </entity> </entities> diff --git a/app/code/Magento/Shipping/Test/Mftf/composer.json b/app/code/Magento/Shipping/Test/Mftf/composer.json deleted file mode 100644 index b5dd22735626d..0000000000000 --- a/app/code/Magento/Shipping/Test/Mftf/composer.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "magento/functional-test-module-shipping", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-contact": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-user": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-fedex": "100.0.0-dev", - "magento/functional-test-module-ups": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Shipping/composer.json b/app/code/Magento/Shipping/composer.json index 2935df2e45542..b4e77dfdeb2d9 100644 --- a/app/code/Magento/Shipping/composer.json +++ b/app/code/Magento/Shipping/composer.json @@ -25,7 +25,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Signifyd/Model/CaseServices/UpdatingService.php b/app/code/Magento/Signifyd/Model/CaseServices/UpdatingService.php index 870705db941cc..168ab67f8cf50 100644 --- a/app/code/Magento/Signifyd/Model/CaseServices/UpdatingService.php +++ b/app/code/Magento/Signifyd/Model/CaseServices/UpdatingService.php @@ -5,8 +5,8 @@ */ namespace Magento\Signifyd\Model\CaseServices; +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\Exception\NotFoundException; use Magento\Signifyd\Api\CaseRepositoryInterface; use Magento\Signifyd\Api\Data\CaseInterface; use Magento\Signifyd\Model\CommentsHistoryUpdater; @@ -73,7 +73,6 @@ public function __construct( * @param CaseInterface $case * @param array $data * @return void - * @throws NotFoundException * @throws LocalizedException */ public function update(CaseInterface $case, array $data) @@ -111,7 +110,7 @@ private function setCaseData(CaseInterface $case, array $data) 'orderId' ]; foreach ($data as $key => $value) { - $methodName = 'set' . ucfirst($key); + $methodName = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key); if (!in_array($key, $notResolvedKeys) && method_exists($case, $methodName)) { call_user_func([$case, $methodName], $value); } diff --git a/app/code/Magento/Signifyd/Test/Mftf/composer.json b/app/code/Magento/Signifyd/Test/Mftf/composer.json deleted file mode 100644 index dde4d64ed7af3..0000000000000 --- a/app/code/Magento/Signifyd/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-signifyd", - "description": "Submitting Case Entry to Signifyd on Order Creation", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Signifyd/composer.json b/app/code/Magento/Signifyd/composer.json index cded060a54518..51e290957ec9a 100644 --- a/app/code/Magento/Signifyd/composer.json +++ b/app/code/Magento/Signifyd/composer.json @@ -17,7 +17,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "proprietary" ], diff --git a/app/code/Magento/Sitemap/Block/Adminhtml/Grid/Renderer/Link.php b/app/code/Magento/Sitemap/Block/Adminhtml/Grid/Renderer/Link.php index ffc2bf5f6d1cf..ac845ce8cb858 100644 --- a/app/code/Magento/Sitemap/Block/Adminhtml/Grid/Renderer/Link.php +++ b/app/code/Magento/Sitemap/Block/Adminhtml/Grid/Renderer/Link.php @@ -62,6 +62,7 @@ public function render(\Magento\Framework\DataObject $row) { /** @var $sitemap \Magento\Sitemap\Model\Sitemap */ $sitemap = $this->_sitemapFactory->create(); + $sitemap->setStoreId($row->getStoreId()); $url = $this->escapeHtml($sitemap->getSitemapUrl($row->getSitemapPath(), $row->getSitemapFilename())); $fileName = preg_replace('/^\//', '', $row->getSitemapPath() . $row->getSitemapFilename()); diff --git a/app/code/Magento/Sitemap/Test/Mftf/composer.json b/app/code/Magento/Sitemap/Test/Mftf/composer.json deleted file mode 100644 index 046761ba34e80..0000000000000 --- a/app/code/Magento/Sitemap/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-sitemap", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-robots": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Sitemap/composer.json b/app/code/Magento/Sitemap/composer.json index 08fa3badf78e2..cda61e63e35cf 100644 --- a/app/code/Magento/Sitemap/composer.json +++ b/app/code/Magento/Sitemap/composer.json @@ -18,7 +18,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php index 9875e700d0afd..1203f748c0615 100644 --- a/app/code/Magento/Store/Model/Store.php +++ b/app/code/Magento/Store/Model/Store.php @@ -901,23 +901,17 @@ public function setCurrentCurrencyCode($code) */ public function getCurrentCurrencyCode() { + $availableCurrencyCodes = \array_values($this->getAvailableCurrencyCodes(true)); // try to get currently set code among allowed - $code = $this->_httpContext->getValue(Context::CONTEXT_CURRENCY); - $code = $code === null ? $this->_getSession()->getCurrencyCode() : $code; - if (empty($code)) { + $code = $this->_httpContext->getValue(Context::CONTEXT_CURRENCY) ?? $this->_getSession()->getCurrencyCode(); + if (empty($code) || !\in_array($code, $availableCurrencyCodes)) { $code = $this->getDefaultCurrencyCode(); - } - if (in_array($code, $this->getAvailableCurrencyCodes(true))) { - return $code; + if (!\in_array($code, $availableCurrencyCodes) && !empty($availableCurrencyCodes)) { + $code = $availableCurrencyCodes[0]; + } } - // take first one of allowed codes - $codes = array_values($this->getAvailableCurrencyCodes(true)); - if (empty($codes)) { - // return default code, if no codes specified at all - return $this->getDefaultCurrencyCode(); - } - return array_shift($codes); + return $code; } /** diff --git a/app/code/Magento/Store/Model/StoreManager.php b/app/code/Magento/Store/Model/StoreManager.php index a3423b96c3fe4..c47750c0fd8d0 100644 --- a/app/code/Magento/Store/Model/StoreManager.php +++ b/app/code/Magento/Store/Model/StoreManager.php @@ -234,11 +234,11 @@ public function getWebsites($withDefault = false, $codeKey = false) public function reinitStores() { $this->currentStoreId = null; - $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, [StoreResolver::CACHE_TAG, Store::CACHE_TAG]); - $this->scopeConfig->clean(); $this->storeRepository->clean(); $this->websiteRepository->clean(); $this->groupRepository->clean(); + $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, [StoreResolver::CACHE_TAG, Store::CACHE_TAG]); + $this->scopeConfig->clean(); } /** diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml index 24c1a79a6959c..9361c7d942c67 100644 --- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml @@ -16,6 +16,7 @@ <amOnPage url="{{AdminSystemStoreWebsitePage.url}}" stepKey="navigateToNewWebsitePage"/> <waitForPageLoad stepKey="waitForStoresPageLoad"/> <!--Create Website--> + <waitForElementVisible selector="{{AdminNewWebsiteSection.name}}" stepKey="waitForWebsiteFormAppeared" /> <fillField selector="{{AdminNewWebsiteSection.name}}" userInput="{{newWebsiteName}}" stepKey="enterWebsiteName" /> <fillField selector="{{AdminNewWebsiteSection.code}}" userInput="{{websiteCode}}" stepKey="enterWebsiteCode" /> <click selector="{{AdminNewWebsiteActionsSection.saveWebsite}}" stepKey="clickSaveWebsite" /> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml index 3e38d9aa53c9f..f85125bcf3291 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml @@ -48,6 +48,15 @@ <data key="store_type">store</data> <requiredEntity type="storeGroup">secondStoreGroup</requiredEntity> </entity> + <entity name="SecondStoreUnique" type="store"> + <data key="name" unique="suffix">Second Store View </data> + <data key="code" unique="suffix">second_store_view_</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_action">add</data> + <data key="store_type">store</data> + <requiredEntity type="storeGroup">secondStoreGroup</requiredEntity> + </entity> <entity name="staticStore" type="store"> <data key="name">Second Store View</data> <data key="code">store123</data> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml index 30dfbd9dbedf2..e45653ca3a796 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreGroupData.xml @@ -29,6 +29,14 @@ <data key="store_action">add</data> <data key="store_type">group</data> </entity> + <entity name="SecondStoreGroupUnique" type="group"> + <data key="group_id">null</data> + <data key="name" unique="suffix">Second Store </data> + <data key="code" unique="suffix">second_store_</data> + <var key="root_category_id" entityKey="id" entityType="category"/> + <data key="store_action">add</data> + <data key="store_type">group</data> + </entity> <entity name="staticStoreGroup" type="group"> <data key="name">NewStore</data> <data key="code">Base12</data> diff --git a/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml new file mode 100644 index 0000000000000..22c5d747748cd --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd"> + <entity name="_defaultWebsite" type="group"> + <data key="name">Main Website</data> + <data key="code">base</data> + <data key="sort_order">1</data> + </entity> + <entity name="CustomWebSite" type="website"> + <data key="name" unique="suffix">website</data> + <data key="code" unique="suffix">website</data> + <data key="sort_order">2</data> + </entity> + <entity name="SecondWebsite" type="website"> + <data key="name" unique="suffix">Second Website </data> + <data key="code" unique="suffix">second_website_</data> + <data key="sort_order">10</data> + </entity> +</entities> diff --git a/app/code/Magento/Store/Test/Mftf/Metadata/website-meta.xml b/app/code/Magento/Store/Test/Mftf/Metadata/website-meta.xml new file mode 100644 index 0000000000000..bad274501f710 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Metadata/website-meta.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd"> + <operation name="CreateWebsite" dataType="website" type="create" + auth="adminFormKey" url="/admin/system_store/save" method="POST" successRegex="/messages-message-success/" returnRegex=""> + <object dataType="website" key="website"> + <field key="website_id">string</field> + <field key="name">string</field> + <field key="code">string</field> + <field key="sort_order">integer</field> + </object> + <field key="store_action">string</field> + <field key="store_type">string</field> + </operation> +</operations> diff --git a/app/code/Magento/Store/Test/Mftf/composer.json b/app/code/Magento/Store/Test/Mftf/composer.json deleted file mode 100644 index cf00d3bff3b18..0000000000000 --- a/app/code/Magento/Store/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-store", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-deploy": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Store/Test/Unit/Model/StoreTest.php b/app/code/Magento/Store/Test/Unit/Model/StoreTest.php index 955f781a3d1ef..b83a297289fda 100644 --- a/app/code/Magento/Store/Test/Unit/Model/StoreTest.php +++ b/app/code/Magento/Store/Test/Unit/Model/StoreTest.php @@ -10,6 +10,7 @@ use Magento\Framework\App\Config\ReinitableConfigInterface; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\Session\SessionManagerInterface; use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; @@ -40,6 +41,16 @@ class StoreTest extends \PHPUnit\Framework\TestCase */ protected $filesystemMock; + /** + * @var ReinitableConfigInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $configMock; + + /** + * @var SessionManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $sessionMock; + /** * @var \Magento\Framework\Url\ModifierInterface|\PHPUnit_Framework_MockObject_MockObject */ @@ -60,12 +71,22 @@ protected function setUp() 'isSecure', 'getServer', ]); + $this->filesystemMock = $this->getMockBuilder(\Magento\Framework\Filesystem::class) ->disableOriginalConstructor() ->getMock(); + $this->configMock = $this->getMockBuilder(ReinitableConfigInterface::class) + ->getMock(); + $this->sessionMock = $this->getMockBuilder(SessionManagerInterface::class) + ->setMethods(['getCurrencyCode']) + ->getMockForAbstractClass(); $this->store = $this->objectManagerHelper->getObject( \Magento\Store\Model\Store::class, - ['filesystem' => $this->filesystemMock] + [ + 'filesystem' => $this->filesystemMock, + 'config' => $this->configMock, + 'session' => $this->sessionMock, + ] ); $this->urlModifierMock = $this->createMock(\Magento\Framework\Url\ModifierInterface::class); @@ -671,4 +692,78 @@ private function setUrlModifier(\Magento\Store\Model\Store $model) $property->setAccessible(true); $property->setValue($model, $this->urlModifierMock); } + + /** + * @param array $availableCodes + * @param string $currencyCode + * @param string $defaultCode + * @param string $expectedCode + * @return void + * @dataProvider currencyCodeDataProvider + */ + public function testGetCurrentCurrencyCode( + array $availableCodes, + string $currencyCode, + string $defaultCode, + string $expectedCode + ) { + $this->store->setData('available_currency_codes', $availableCodes); + $this->sessionMock->method('getCurrencyCode') + ->willReturn($currencyCode); + $this->configMock->method('getValue') + ->with(\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_DEFAULT) + ->willReturn($defaultCode); + + $code = $this->store->getCurrentCurrencyCode(); + $this->assertEquals($expectedCode, $code); + } + + /** + * @return array + */ + public function currencyCodeDataProvider(): array + { + return [ + [ + [ + 'USD', + ], + 'USD', + 'USD', + 'USD', + ], + [ + [ + 'USD', + 'EUR', + ], + 'EUR', + 'USD', + 'EUR', + ], + [ + [ + 'EUR', + 'USD', + ], + 'GBP', + 'USD', + 'USD', + ], + [ + [ + 'USD', + ], + 'GBP', + 'EUR', + 'USD', + ], + [ + [], + 'GBP', + 'EUR', + 'EUR', + ], + ]; + } } diff --git a/app/code/Magento/Store/composer.json b/app/code/Magento/Store/composer.json index 875b4bb03d454..6f2d88d6f6fcb 100644 --- a/app/code/Magento/Store/composer.json +++ b/app/code/Magento/Store/composer.json @@ -14,7 +14,7 @@ "magento/module-deploy": "100.2.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Swagger/Test/Mftf/composer.json b/app/code/Magento/Swagger/Test/Mftf/composer.json deleted file mode 100644 index 31cd460b41d56..0000000000000 --- a/app/code/Magento/Swagger/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-swagger", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Swagger/composer.json b/app/code/Magento/Swagger/composer.json index b30ea4cfe46b6..0cdf3dadb2b8a 100644 --- a/app/code/Magento/Swagger/composer.json +++ b/app/code/Magento/Swagger/composer.json @@ -6,7 +6,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/SwaggerWebapi/Test/Mftf/composer.json b/app/code/Magento/SwaggerWebapi/Test/Mftf/composer.json deleted file mode 100644 index 6a2e227cd31b2..0000000000000 --- a/app/code/Magento/SwaggerWebapi/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-swagger-webapi", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-swagger": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SwaggerWebapi/composer.json b/app/code/Magento/SwaggerWebapi/composer.json index 7f9b2f258d6f8..5a72cb88c4f3a 100644 --- a/app/code/Magento/SwaggerWebapi/composer.json +++ b/app/code/Magento/SwaggerWebapi/composer.json @@ -4,10 +4,10 @@ "require": { "php": "~7.0.13|~7.1.0", "magento/framework": "101.0.*", - "magento/module-swagger": "*" + "magento/module-swagger": "100.2.*" }, "type": "magento2-module", - "version": "100.0.0", + "version": "100.2.0", "license": [ "OSL-3.0", "AFL-3.0" @@ -20,4 +20,4 @@ "Magento\\SwaggerWebapi\\": "" } } -} \ No newline at end of file +} diff --git a/app/code/Magento/Swatches/Helper/Data.php b/app/code/Magento/Swatches/Helper/Data.php index c17dc0b10a986..6f751068d543a 100644 --- a/app/code/Magento/Swatches/Helper/Data.php +++ b/app/code/Magento/Swatches/Helper/Data.php @@ -499,10 +499,10 @@ private function addFallbackOptions(array $fallbackValues, array $swatches) && $swatches[$optionId]['type'] === $optionsArray[$currentStoreId]['type'] ) { $swatches[$optionId] = $optionsArray[$currentStoreId]; - } else { - if (isset($optionsArray[self::DEFAULT_STORE_ID])) { - $swatches[$optionId] = $optionsArray[self::DEFAULT_STORE_ID]; - } + } elseif (isset($optionsArray[$currentStoreId])) { + $swatches[$optionId] = $optionsArray[$currentStoreId]; + } elseif (isset($optionsArray[self::DEFAULT_STORE_ID])) { + $swatches[$optionId] = $optionsArray[self::DEFAULT_STORE_ID]; } } diff --git a/app/code/Magento/Swatches/Test/Mftf/composer.json b/app/code/Magento/Swatches/Test/Mftf/composer.json deleted file mode 100644 index 6c55c78bc4e53..0000000000000 --- a/app/code/Magento/Swatches/Test/Mftf/composer.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "magento/functional-test-module-swatches", - "description": "Add Swatches to Products", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-layered-navigation": "100.0.0-dev", - "magento/functional-test-module-swatches-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Swatches/composer.json b/app/code/Magento/Swatches/composer.json index 4949931f4db2d..26d6325447b2e 100644 --- a/app/code/Magento/Swatches/composer.json +++ b/app/code/Magento/Swatches/composer.json @@ -19,7 +19,7 @@ "magento/module-swatches-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "proprietary" ], diff --git a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js index 1a58e4b6f2e7a..4c9df6ef657d7 100644 --- a/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js +++ b/app/code/Magento/Swatches/view/adminhtml/web/js/product-attributes.js @@ -45,7 +45,7 @@ define([ get tabsFront() { return this.attrTabsFront.length ? this.attrTabsFront.closest('li') : $('#front_fieldset-wrapper'); }, - selectFields: ['select', 'multiselect', 'price', 'swatch_text', 'swatch_visual'], + selectFields: ['boolean', 'select', 'multiselect', 'price', 'swatch_text', 'swatch_visual'], /** * @this {swatchProductAttributes} diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml index f8c55d6426982..f19da436a60c4 100644 --- a/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml +++ b/app/code/Magento/Swatches/view/frontend/templates/product/listing/renderer.phtml @@ -21,7 +21,8 @@ "numberToShow": <?= /* @escapeNotVerified */ $block->getNumberSwatchesPerProduct(); ?>, "jsonConfig": <?= /* @escapeNotVerified */ $block->getJsonConfig(); ?>, "jsonSwatchConfig": <?= /* @escapeNotVerified */ $block->getJsonSwatchConfig(); ?>, - "mediaCallback": "<?= /* @escapeNotVerified */ $block->getMediaCallback() ?>" + "mediaCallback": "<?= /* @escapeNotVerified */ $block->getMediaCallback() ?>", + "jsonSwatchImageSizeConfig": <?= /* @noEscape */ $block->getJsonSwatchSizeConfig() ?> } } } diff --git a/app/code/Magento/SwatchesLayeredNavigation/Test/Mftf/composer.json b/app/code/Magento/SwatchesLayeredNavigation/Test/Mftf/composer.json deleted file mode 100644 index 2001b4a42691c..0000000000000 --- a/app/code/Magento/SwatchesLayeredNavigation/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-swatches-layered-navigation", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/magento-composer-installer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/SwatchesLayeredNavigation/composer.json b/app/code/Magento/SwatchesLayeredNavigation/composer.json index b10747614cb90..d3d4cba7af291 100644 --- a/app/code/Magento/SwatchesLayeredNavigation/composer.json +++ b/app/code/Magento/SwatchesLayeredNavigation/composer.json @@ -7,7 +7,7 @@ "magento/magento-composer-installer": "*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Save.php b/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Save.php index 4eaaa3be8a8f2..50e7437396b0c 100644 --- a/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Save.php +++ b/app/code/Magento/Tax/Block/Adminhtml/Rate/Toolbar/Save.php @@ -115,7 +115,7 @@ protected function _prepareLayout() ['label' => __('Reset'), 'onclick' => 'window.location.reload()', 'class' => 'reset'] ); - $rate = intval($this->getRequest()->getParam('rate')); + $rate = (int)$this->getRequest()->getParam('rate'); if ($rate) { $this->buttonList->add( 'delete', diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/Tax.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/Tax.php index 4aea7ab4c5a7c..82e2048283a9b 100755 --- a/app/code/Magento/Tax/Model/Sales/Total/Quote/Tax.php +++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/Tax.php @@ -164,9 +164,8 @@ protected function clearValues(Address\Total $total) $total->setBaseShippingInclTax(0); $total->setShippingTaxAmount(0); $total->setBaseShippingTaxAmount(0); - $total->setShippingAmountForDiscount(0); - $total->setBaseShippingAmountForDiscount(0); - $total->setBaseShippingAmountForDiscount(0); + $total->setShippingAmountForDiscount(null); + $total->setBaseShippingAmountForDiscount(null); $total->setTotalAmount('extra_tax', 0); $total->setBaseTotalAmount('extra_tax', 0); } diff --git a/app/code/Magento/Tax/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Tax/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml similarity index 92% rename from app/code/Magento/Tax/Test/Mftf/Section/CheckoutCartSummarySection.xml rename to app/code/Magento/Tax/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml index e01afa52dc09a..81bdd4103e83a 100644 --- a/app/code/Magento/Tax/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/Tax/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml @@ -8,7 +8,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - <section name="CheckoutCartSummarySection"> + <section name="StorefrontCheckoutCartSummarySection"> <element name="taxAmount" type="text" selector="[data-th='Tax']>span"/> <element name="taxSummary" type="text" selector=".totals-tax-summary" timeout="5"/> <element name="rate" type="text" selector=" tr.totals-tax-details.shown th.mark"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml index 1a92e6feaf705..3c6953f08e8d7 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerPhysicalQuoteTest.xml @@ -86,34 +86,34 @@ <!-- Step 3: Go to Shopping Cart --> <actionGroup ref="StorefrontViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> - <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> - <see selector="{{CheckoutCartSummarySection.country}}" userInput="$$createCustomer.country_id$$" stepKey="checkCustomerCountry" /> - <see selector="{{CheckoutCartSummarySection.region}}" userInput="$$createCustomer.state$$" stepKey="checkCustomerRegion" /> - <see selector="{{CheckoutCartSummarySection.postcode}}" userInput="$$createCustomer.postcode$$" stepKey="checkCustomerPostcode" /> - <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$10" stepKey="checkAmountFPTCA" /> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.83" stepKey="checkTaxAmountCA" /> - <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> - <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> - <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> + <conditionalClick selector="{{StorefrontCheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{StorefrontCheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <see selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="$$createCustomer.country_id$$" stepKey="checkCustomerCountry" /> + <see selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="$$createCustomer.state$$" stepKey="checkCustomerRegion" /> + <see selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="$$createCustomer.postcode$$" stepKey="checkCustomerPostcode" /> + <see selector="{{StorefrontCheckoutCartSummarySection.amountFPT}}" userInput="$10" stepKey="checkAmountFPTCA" /> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$0.83" stepKey="checkTaxAmountCA" /> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> + <click selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> + <see selector="{{StorefrontCheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> <!-- Step 5: Change Data --> - <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> - <selectOption selector="{{CheckoutCartSummarySection.region}}" userInput="Aargau" stepKey="selectAargauRegion"/> - <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="Aargau" stepKey="selectAargauRegion"/> + <fillField selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> <!-- Step 6: Select shipping rate again(it need for get new totals request - performance reason) --> - <click selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod"/> - <scrollTo selector="{{CheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> - <dontSeeElement selector="{{CheckoutCartSummarySection.amountFPT}}" stepKey="checkFPTIsNotDisplayed" /> + <click selector="{{StorefrontCheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod"/> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> + <dontSeeElement selector="{{StorefrontCheckoutCartSummarySection.amountFPT}}" stepKey="checkFPTIsNotDisplayed" /> <!-- Step 7: Change Data --> - <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> - <selectOption selector="{{CheckoutCartSummarySection.region}}" userInput="New York" stepKey="selectNewYorkRegion"/> - <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="New York" stepKey="selectNewYorkRegion"/> + <fillField selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> <!-- Step 8: Select shipping rate again(it need for get new totals request - performance reason) --> - <click selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod2"/> - <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> - <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary3"/> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.84" stepKey="checkTaxAmountNY" /> - <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> - <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$20" stepKey="checkAmountFPTNY" /> + <click selector="{{StorefrontCheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod2"/> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> + <click selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary3"/> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$0.84" stepKey="checkTaxAmountNY" /> + <see selector="{{StorefrontCheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> + <see selector="{{StorefrontCheckoutCartSummarySection.amountFPT}}" userInput="$20" stepKey="checkAmountFPTNY" /> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml index afe775fc372bc..fbb44807c8b14 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForCustomerVirtualQuoteTest.xml @@ -58,26 +58,26 @@ <!-- Step 3: Go to Shopping Cart --> <actionGroup ref="StorefrontViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> - <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> - <see selector="{{CheckoutCartSummarySection.country}}" userInput="$$createCustomer.country_id$$" stepKey="checkCustomerCountry" /> - <see selector="{{CheckoutCartSummarySection.region}}" userInput="$$createCustomer.state$$" stepKey="checkCustomerRegion" /> - <see selector="{{CheckoutCartSummarySection.postcode}}" userInput="$$createCustomer.postcode$$" stepKey="checkCustomerPostcode" /> - <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> - <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="expandTaxSummary"/> - <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> + <conditionalClick selector="{{StorefrontCheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{StorefrontCheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <see selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="$$createCustomer.country_id$$" stepKey="checkCustomerCountry" /> + <see selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="$$createCustomer.state$$" stepKey="checkCustomerRegion" /> + <see selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="$$createCustomer.postcode$$" stepKey="checkCustomerPostcode" /> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> + <click selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="expandTaxSummary"/> + <see selector="{{StorefrontCheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> <!-- Step 5: Change Data --> - <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> - <selectOption selector="{{CheckoutCartSummarySection.region}}" userInput="Aargau" stepKey="selectAargauRegion"/> - <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> - <scrollTo selector="{{CheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="Aargau" stepKey="selectAargauRegion"/> + <fillField selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> <!-- Step 6: Change Data --> - <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> - <selectOption selector="{{CheckoutCartSummarySection.region}}" userInput="California" stepKey="selectCaliforniaRegion"/> - <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="90230" stepKey="inputPostCode2"/> - <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> - <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$3.30" stepKey="checkTaxAmount2" /> - <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="California" stepKey="selectCaliforniaRegion"/> + <fillField selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="90230" stepKey="inputPostCode2"/> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> + <click selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$3.30" stepKey="checkTaxAmount2" /> + <see selector="{{StorefrontCheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml index 893c740321314..93fd28e2ea611 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestPhysicalQuoteTest.xml @@ -82,31 +82,31 @@ <!-- Step 3: Go to Shopping Cart --> <actionGroup ref="StorefrontViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> - <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> - <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$10" stepKey="checkAmountFPTCA" /> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.83" stepKey="checkTaxAmountCA" /> - <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> - <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> - <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> + <conditionalClick selector="{{StorefrontCheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{StorefrontCheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <see selector="{{StorefrontCheckoutCartSummarySection.amountFPT}}" userInput="$10" stepKey="checkAmountFPTCA" /> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$0.83" stepKey="checkTaxAmountCA" /> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> + <click selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> + <see selector="{{StorefrontCheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> <!-- Step 5: Change Data --> - <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> - <selectOption selector="{{CheckoutCartSummarySection.region}}" userInput="Aargau" stepKey="selectAargauRegion"/> - <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="Aargau" stepKey="selectAargauRegion"/> + <fillField selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> <!-- Step 6: Select shipping rate again(it need for get new totals request - performance reason) --> - <click selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod"/> - <scrollTo selector="{{CheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> - <dontSeeElement selector="{{CheckoutCartSummarySection.amountFPT}}" stepKey="checkFPTIsNotDisplayed" /> + <click selector="{{StorefrontCheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod"/> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" stepKey="scrollToTaxSummary2" /> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> + <dontSeeElement selector="{{StorefrontCheckoutCartSummarySection.amountFPT}}" stepKey="checkFPTIsNotDisplayed" /> <!-- Step 7: Change Data --> - <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> - <selectOption selector="{{CheckoutCartSummarySection.region}}" userInput="New York" stepKey="selectNewYorkRegion"/> - <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="New York" stepKey="selectNewYorkRegion"/> + <fillField selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> <!-- Step 8: Select shipping rate again(it need for get new totals request - performance reason) --> - <click selector="{{CheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod2"/> - <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> - <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.84" stepKey="checkTaxAmountNY" /> - <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> - <see selector="{{CheckoutCartSummarySection.amountFPT}}" userInput="$20" stepKey="checkAmountFPTNY" /> + <click selector="{{StorefrontCheckoutCartSummarySection.flatRateShippingMethod}}" stepKey="selectflatRateShippingMethodShippingMethod2"/> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary3" /> + <click selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$0.84" stepKey="checkTaxAmountNY" /> + <see selector="{{StorefrontCheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> + <see selector="{{StorefrontCheckoutCartSummarySection.amountFPT}}" userInput="$20" stepKey="checkAmountFPTNY" /> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml index 37c1083722a25..b2e484eb66d0c 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxInformationInShoppingCartForGuestVirtualQuoteTest.xml @@ -54,23 +54,23 @@ <!-- Step 3: Go to Shopping Cart --> <actionGroup ref="StorefrontViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> <!-- Step 4: Open Estimate Shipping and Tax section --> - <conditionalClick selector="{{CheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{CheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> - <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$3.30" stepKey="checkTaxAmountCA" /> - <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> - <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> + <conditionalClick selector="{{StorefrontCheckoutCartSummarySection.estimateShippingAndTax}}" dependentSelector="{{StorefrontCheckoutCartSummarySection.country}}" visible="false" stepKey="expandEstimateShippingandTax" /> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary" /> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$3.30" stepKey="checkTaxAmountCA" /> + <click selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary"/> + <see selector="{{StorefrontCheckoutCartSummarySection.rate}}" userInput="US-CA-*-Rate 1 (8.25%)" stepKey="checkRateCA" /> <!-- Step 5: Change Data --> - <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> - <selectOption selector="{{CheckoutCartSummarySection.region}}" userInput="Aargau" stepKey="selectAargauRegion"/> - <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="Switzerland" stepKey="selectSwitzerlandCountry"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="Aargau" stepKey="selectAargauRegion"/> + <fillField selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="1234" stepKey="inputPostCode"/> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$0.00" stepKey="checkTaxAmount" /> <!-- Step 6: Change Data --> - <selectOption selector="{{CheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> - <selectOption selector="{{CheckoutCartSummarySection.region}}" userInput="New York" stepKey="selectNewYorkRegion"/> - <fillField selector="{{CheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> - <scrollTo selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary2" /> - <click selector="{{CheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> - <see selector="{{CheckoutCartSummarySection.taxAmount}}" userInput="$3.35" stepKey="checkTaxAmountNY" /> - <see selector="{{CheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.country}}" userInput="United States" stepKey="selectUnitedStatesCountry"/> + <selectOption selector="{{StorefrontCheckoutCartSummarySection.region}}" userInput="New York" stepKey="selectNewYorkRegion"/> + <fillField selector="{{StorefrontCheckoutCartSummarySection.postcode}}" userInput="12345" stepKey="inputPostCode2"/> + <scrollTo selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="scrollToTaxSummary2" /> + <click selector="{{StorefrontCheckoutCartSummarySection.taxSummary}}" stepKey="taxSummary2"/> + <see selector="{{StorefrontCheckoutCartSummarySection.taxAmount}}" userInput="$3.35" stepKey="checkTaxAmountNY" /> + <see selector="{{StorefrontCheckoutCartSummarySection.rate}}" userInput="US-NY-*-Rate 1 (8.375%)" stepKey="checkRateNY" /> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/composer.json b/app/code/Magento/Tax/Test/Mftf/composer.json deleted file mode 100644 index 9701eedd15c0d..0000000000000 --- a/app/code/Magento/Tax/Test/Mftf/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "magento/functional-test-module-tax", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-reports": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-tax-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Tax/composer.json b/app/code/Magento/Tax/composer.json index 6ac542c04e3d4..34c3ec567467a 100644 --- a/app/code/Magento/Tax/composer.json +++ b/app/code/Magento/Tax/composer.json @@ -22,7 +22,7 @@ "magento/module-tax-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/TaxImportExport/Test/Mftf/composer.json b/app/code/Magento/TaxImportExport/Test/Mftf/composer.json deleted file mode 100644 index 00dec78135e89..0000000000000 --- a/app/code/Magento/TaxImportExport/Test/Mftf/composer.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "magento/functional-test-module-tax-import-export", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/TaxImportExport/composer.json b/app/code/Magento/TaxImportExport/composer.json index 9be5e65dd6364..927aac1e59501 100644 --- a/app/code/Magento/TaxImportExport/composer.json +++ b/app/code/Magento/TaxImportExport/composer.json @@ -10,7 +10,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Theme/Block/Html/Pager.php b/app/code/Magento/Theme/Block/Html/Pager.php index d4cfe544f4792..3d66113ac613d 100644 --- a/app/code/Magento/Theme/Block/Html/Pager.php +++ b/app/code/Magento/Theme/Block/Html/Pager.php @@ -580,7 +580,7 @@ public function getJump() */ public function setFrameLength($frame) { - $frame = abs(intval($frame)); + $frame = abs((int)$frame); if ($frame == 0) { $frame = $this->_frameLength; } @@ -600,7 +600,7 @@ public function setFrameLength($frame) */ public function setJump($jump) { - $jump = abs(intval($jump)); + $jump = abs((int)$jump); if ($this->getJump() != $jump) { $this->_setFrameInitialized(false); $this->_jump = $jump; diff --git a/app/code/Magento/Theme/Model/Wysiwyg/Storage.php b/app/code/Magento/Theme/Model/Wysiwyg/Storage.php index 82206b8eb1c65..eb0412738d1ea 100644 --- a/app/code/Magento/Theme/Model/Wysiwyg/Storage.php +++ b/app/code/Magento/Theme/Model/Wysiwyg/Storage.php @@ -127,14 +127,6 @@ public function uploadFile($targetPath) $this->_createThumbnail($targetPath . '/' . $uploader->getUploadedFileName()); - $result['cookie'] = [ - 'name' => $this->_helper->getSession()->getName(), - 'value' => $this->_helper->getSession()->getSessionId(), - 'lifetime' => $this->_helper->getSession()->getCookieLifetime(), - 'path' => $this->_helper->getSession()->getCookiePath(), - 'domain' => $this->_helper->getSession()->getCookieDomain() - ]; - return $result; } diff --git a/app/code/Magento/Theme/Test/Mftf/composer.json b/app/code/Magento/Theme/Test/Mftf/composer.json deleted file mode 100644 index d45c901981449..0000000000000 --- a/app/code/Magento/Theme/Test/Mftf/composer.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "magento/functional-test-module-theme", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-widget": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/functional-test-module-media-storage": "100.0.0-dev", - "magento/functional-test-module-ui": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-require-js": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-translation": "100.0.0-dev", - "magento/functional-test-module-theme-sample-data": "100.0.0-dev", - "magento/functional-test-module-deploy": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Theme/Test/Unit/Model/Wysiwyg/StorageTest.php b/app/code/Magento/Theme/Test/Unit/Model/Wysiwyg/StorageTest.php index 913561590607a..2a0ac560f17dc 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/Wysiwyg/StorageTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/Wysiwyg/StorageTest.php @@ -152,8 +152,7 @@ public function testUploadFile() $this->_helperStorage->expects($this->any())->method('getSession')->will($this->returnValue($session)); $expectedResult = [ - 'not_empty', - 'cookie' => ['name' => null, 'value' => null, 'lifetime' => null, 'path' => null, 'domain' => null], + 'not_empty' ]; $this->assertEquals($expectedResult, $this->_storageModel->uploadFile($this->_storageRoot)); diff --git a/app/code/Magento/Theme/composer.json b/app/code/Magento/Theme/composer.json index dbf28aa03c9fe..d2ac3a95d923d 100644 --- a/app/code/Magento/Theme/composer.json +++ b/app/code/Magento/Theme/composer.json @@ -22,7 +22,7 @@ "magento/module-directory": "100.2.*" }, "type": "magento2-module", - "version": "100.2.5", + "version": "100.2.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Theme/etc/config.xml b/app/code/Magento/Theme/etc/config.xml index a6984b449d944..b44691c0df963 100644 --- a/app/code/Magento/Theme/etc/config.xml +++ b/app/code/Magento/Theme/etc/config.xml @@ -46,6 +46,7 @@ Disallow: /*SID= </header> <footer translate="copyright"> <copyright>Copyright © 2013-present Magento, Inc. All rights reserved.</copyright> + <report_bugs>1</report_bugs> </footer> </design> <theme> diff --git a/app/code/Magento/Theme/etc/di.xml b/app/code/Magento/Theme/etc/di.xml index 21e1736d01bf1..a4bfaa18a2994 100644 --- a/app/code/Magento/Theme/etc/di.xml +++ b/app/code/Magento/Theme/etc/di.xml @@ -213,6 +213,10 @@ <item name="path" xsi:type="string">design/footer/absolute_footer</item> <item name="fieldset" xsi:type="string">other_settings/footer</item> </item> + <item name="footer_report_bugs" xsi:type="array"> + <item name="path" xsi:type="string">design/footer/report_bugs</item> + <item name="fieldset" xsi:type="string">other_settings/footer</item> + </item> <item name="default_robots" xsi:type="array"> <item name="path" xsi:type="string">design/search_engine_robots/default_robots</item> <item name="fieldset" xsi:type="string">other_settings/search_engine_robots</item> diff --git a/app/code/Magento/Theme/i18n/en_US.csv b/app/code/Magento/Theme/i18n/en_US.csv index b454b7222bd1c..e18798f4bf165 100644 --- a/app/code/Magento/Theme/i18n/en_US.csv +++ b/app/code/Magento/Theme/i18n/en_US.csv @@ -185,3 +185,7 @@ Settings,Settings "2 columns with left bar","2 columns with left bar" "2 columns with right bar","2 columns with right bar" "3 columns","3 columns" +ID,ID +View,View +Action,Action +"Display Report Bugs Link","Display Report Bugs Link" diff --git a/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml b/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml index ac734699e4d71..3dcc265147cf2 100644 --- a/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml +++ b/app/code/Magento/Theme/view/adminhtml/ui_component/design_config_form.xml @@ -234,6 +234,20 @@ <dataScope>footer_copyright</dataScope> </settings> </field> + <field name="footer_report_bugs" formElement="select"> + <settings> + <dataType>text</dataType> + <label translate="true">Display Report Bugs Link</label> + <dataScope>footer_report_bugs</dataScope> + </settings> + <formElements> + <select> + <settings> + <options class="Magento\Config\Model\Config\Source\Yesno"/> + </settings> + </select> + </formElements> + </field> </fieldset> <fieldset name="search_engine_robots" sortOrder="120"> <settings> diff --git a/app/code/Magento/Theme/view/frontend/layout/default.xml b/app/code/Magento/Theme/view/frontend/layout/default.xml index 716341f5a64a4..c84222be19c3c 100644 --- a/app/code/Magento/Theme/view/frontend/layout/default.xml +++ b/app/code/Magento/Theme/view/frontend/layout/default.xml @@ -119,7 +119,7 @@ </arguments> </block> <block class="Magento\Theme\Block\Html\Footer" name="copyright" template="Magento_Theme::html/copyright.phtml"/> - <block class="Magento\Framework\View\Element\Template" name="report.bugs" template="Magento_Theme::html/bugreport.phtml" /> + <block class="Magento\Framework\View\Element\Template" name="report.bugs" template="Magento_Theme::html/bugreport.phtml" ifconfig="design/footer/report_bugs"/> </container> </referenceContainer> <referenceContainer name="before.body.end"> diff --git a/app/code/Magento/Translation/Test/Mftf/composer.json b/app/code/Magento/Translation/Test/Mftf/composer.json deleted file mode 100644 index 1d012752255e5..0000000000000 --- a/app/code/Magento/Translation/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-translation", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-developer": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-deploy": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Translation/composer.json b/app/code/Magento/Translation/composer.json index 398f2506cb1d6..3a39f30a03e50 100644 --- a/app/code/Magento/Translation/composer.json +++ b/app/code/Magento/Translation/composer.json @@ -12,7 +12,7 @@ "magento/module-deploy": "100.2.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Ui/DataProvider/Modifier/Pool.php b/app/code/Magento/Ui/DataProvider/Modifier/Pool.php index d072fa5b4b8f6..9e9e375083dd1 100644 --- a/app/code/Magento/Ui/DataProvider/Modifier/Pool.php +++ b/app/code/Magento/Ui/DataProvider/Modifier/Pool.php @@ -85,14 +85,7 @@ public function getModifiersInstances() protected function sort(array $data) { usort($data, function (array $a, array $b) { - $a['sortOrder'] = $this->getSortOrder($a); - $b['sortOrder'] = $this->getSortOrder($b); - - if ($a['sortOrder'] == $b['sortOrder']) { - return 0; - } - - return ($a['sortOrder'] < $b['sortOrder']) ? -1 : 1; + return $this->getSortOrder($a) <=> $this->getSortOrder($b); }); return $data; diff --git a/app/code/Magento/Ui/Model/Export/MetadataProvider.php b/app/code/Magento/Ui/Model/Export/MetadataProvider.php index 54d856e8a6104..603e102fa30e0 100644 --- a/app/code/Magento/Ui/Model/Export/MetadataProvider.php +++ b/app/code/Magento/Ui/Model/Export/MetadataProvider.php @@ -60,7 +60,7 @@ public function __construct( Filter $filter, TimezoneInterface $localeDate, ResolverInterface $localeResolver, - $dateFormat = 'M j, Y H:i:s A', + $dateFormat = 'M j, Y h:i:s A', array $data = [] ) { $this->filter = $filter; diff --git a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridFilterActionGroup.xml b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridFilterActionGroup.xml index 2c65f4d548c31..8fbd5342b24c4 100644 --- a/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridFilterActionGroup.xml +++ b/app/code/Magento/Ui/Test/Mftf/ActionGroup/AdminDataGridFilterActionGroup.xml @@ -7,7 +7,7 @@ --> <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <!--Search grid with keyword search--> <actionGroup name="searchAdminDataGridByKeyword"> <arguments> @@ -32,4 +32,17 @@ <waitForPageLoad stepKey="waitForPageLoad"/> <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearExistingOrderFilters"/> </actionGroup> + + <actionGroup name="AdminGridFilterSearchResultsByInput"> + <arguments> + <argument name="inputName" type="string" defaultValue="name"/> + <argument name="value" type="string"/> + </arguments> + <!--Clear all filters in grid if they present--> + <conditionalClick selector="{{AdminDataGridHeaderSection.clearFilters}}" dependentSelector="{{AdminDataGridHeaderSection.clearFilters}}" visible="true" stepKey="clearTheFiltersIfPresent"/> + + <click selector="{{AdminDataGridHeaderSection.filters}}" stepKey="clickOnFilters"/> + <fillField userInput="{{value}}" selector="{{AdminDataGridHeaderSection.filterFieldInput('inputName')}}" stepKey="fillCodeField"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickOnApplyFilters"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml index e5766300b0e87..c23909dfdbe24 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminDataGridHeaderSection.xml @@ -19,6 +19,7 @@ <element name="cancelFilters" type="button" selector="button[data-action='grid-filter-cancel']" timeout="30"/> <element name="applyFilters" type="button" selector="button[data-action='grid-filter-apply']" timeout="30"/> <element name="clearFilters" type="button" selector=".admin__data-grid-header [data-action='grid-filter-reset']" timeout="30"/> + <element name="removeFilter" type="button" selector=".admin__data-grid-filters-current._show .action-remove" timeout="30"/> <!--Grid view bookmarks--> <element name="bookmarkToggle" type="button" selector="div.admin__data-grid-action-bookmarks button[data-bind='toggleCollapsible']" timeout="30"/> <element name="bookmarkOption" type="button" selector="//div[contains(@class, 'admin__data-grid-action-bookmarks')]/ul/li/div/a[text() = '{{label}}']" parameterized="true" timeout="30"/> diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml index 70b6cb5388645..d3e94eb24dfd2 100644 --- a/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml +++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> <section name="AdminMessagesSection"> <element name="successMessage" type="text" selector=".message-success"/> + <element name="errorMessage" type="text" selector=".message.message-error.error"/> </section> </sections> diff --git a/app/code/Magento/Ui/Test/Mftf/composer.json b/app/code/Magento/Ui/Test/Mftf/composer.json deleted file mode 100644 index 6889a12747a2d..0000000000000 --- a/app/code/Magento/Ui/Test/Mftf/composer.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "magento/functional-test-module-ui", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-user": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Ui/Test/Unit/Model/Export/MetadataProviderTest.php b/app/code/Magento/Ui/Test/Unit/Model/Export/MetadataProviderTest.php index adbc6e04636a9..88cbce7576227 100644 --- a/app/code/Magento/Ui/Test/Unit/Model/Export/MetadataProviderTest.php +++ b/app/code/Magento/Ui/Test/Unit/Model/Export/MetadataProviderTest.php @@ -3,9 +3,13 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Ui\Test\Unit\Model\Export; use Magento\Framework\Api\Search\DocumentInterface; +use Magento\Framework\Locale\ResolverInterface; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Framework\View\Element\UiComponentInterface; use Magento\Ui\Component\Listing\Columns; use Magento\Ui\Component\Listing\Columns\Column; @@ -13,29 +17,60 @@ use Magento\Ui\Model\Export\MetadataProvider; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class MetadataProviderTest extends \PHPUnit\Framework\TestCase { /** * @var MetadataProvider */ - protected $model; + private $model; /** * @var Filter | \PHPUnit_Framework_MockObject_MockObject */ - protected $filter; + private $filter; + + /** + * @var TimezoneInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $localeDate; + /** + * @var ResolverInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $localeResolver; + + /** + * @inheritdoc + */ protected function setUp() { $this->filter = $this->getMockBuilder(\Magento\Ui\Component\MassAction\Filter::class) ->disableOriginalConstructor() ->getMock(); + $this->localeDate = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->localeResolver = $this->getMockBuilder(\Magento\Framework\Locale\ResolverInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->localeResolver->expects($this->any()) + ->method('getLocale') + ->willReturn(null); + $objectManager = new ObjectManager($this); $this->model = $objectManager->getObject( \Magento\Ui\Model\Export\MetadataProvider::class, [ 'filter' => $this->filter, + 'localeDate' => $this->localeDate, + 'localeResolver' => $this->localeResolver, + 'data' => ['component_name' => ['field']], ] ); } @@ -96,11 +131,11 @@ public function testGetFields() * @return UiComponentInterface|\PHPUnit_Framework_MockObject_MockObject */ protected function prepareColumns( - $componentName, - $columnName, - $columnLabel, - $columnActionsName = 'actions_name', - $columnActionsLabel = 'actions_label' + string $componentName, + string $columnName, + string $columnLabel, + string $columnActionsName = 'actions_name', + string $columnActionsLabel = 'actions_label' ) { /** @var UiComponentInterface|\PHPUnit_Framework_MockObject_MockObject $component */ $component = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentInterface::class) @@ -163,9 +198,10 @@ protected function prepareColumns( * @param array $fields * @param array $options * @param array $expected + * @return void * @dataProvider getRowDataProvider */ - public function testGetRowData($key, $fields, $options, $expected) + public function testGetRowData(string $key, array $fields, array $options, array $expected) { /** @var DocumentInterface|\PHPUnit_Framework_MockObject_MockObject $document */ $document = $this->getMockBuilder(\Magento\Framework\Api\Search\DocumentInterface::class) @@ -192,7 +228,7 @@ public function testGetRowData($key, $fields, $options, $expected) /** * @return array */ - public function getRowDataProvider() + public function getRowDataProvider(): array { return [ [ @@ -234,9 +270,10 @@ public function getRowDataProvider() * @param string $filter * @param array $options * @param array $expected + * @return void * @dataProvider getOptionsDataProvider */ - public function testGetOptions($filter, $options, $expected) + public function testGetOptions(string $filter, array $options, array $expected) { $component = $this->getMockBuilder(\Magento\Framework\View\Element\UiComponentInterface::class) ->getMockForAbstractClass(); @@ -285,7 +322,7 @@ public function testGetOptions($filter, $options, $expected) /** * @return array */ - public function getOptionsDataProvider() + public function getOptionsDataProvider(): array { return [ [ @@ -347,4 +384,56 @@ public function getOptionsDataProvider() ], ]; } + + /** + * Test for convertDate function. + * + * @param string $fieldValue + * @param string $expected + * @return void + * @dataProvider convertDateProvider + * @covers \Magento\Ui\Model\Export\MetadataProvider::convertDate() + */ + public function testConvertDate(string $fieldValue, string $expected) + { + $componentName = 'component_name'; + /** @var DocumentInterface|\PHPUnit_Framework_MockObject_MockObject $document */ + $document = $this->getMockBuilder(\Magento\Framework\DataObject::class) + ->disableOriginalConstructor() + ->getMock(); + + $document->expects($this->once()) + ->method('getData') + ->with('field') + ->willReturn($fieldValue); + + $this->localeDate->expects($this->once()) + ->method('date') + ->willReturn(new \DateTime($fieldValue, new \DateTimeZone('UTC'))); + + $document->expects($this->once()) + ->method('setData') + ->with('field', $expected); + + $this->model->convertDate($document, $componentName); + } + + /** + * Data provider for testConvertDate. + * + * @return array + */ + public function convertDateProvider(): array + { + return [ + [ + 'fieldValue' => '@1534505233', + 'expected' => 'Aug 17, 2018 11:27:13 AM', + ], + [ + 'fieldValue' => '@1534530000', + 'expected' => 'Aug 17, 2018 06:20:00 PM', + ], + ]; + } } diff --git a/app/code/Magento/Ui/composer.json b/app/code/Magento/Ui/composer.json index 0a2295ba9152c..e259c7d8b0639 100644 --- a/app/code/Magento/Ui/composer.json +++ b/app/code/Magento/Ui/composer.json @@ -13,7 +13,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "101.0.5", + "version": "101.0.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Ui/i18n/en_US.csv b/app/code/Magento/Ui/i18n/en_US.csv index cff52a3fd6fed..981d444b31d53 100644 --- a/app/code/Magento/Ui/i18n/en_US.csv +++ b/app/code/Magento/Ui/i18n/en_US.csv @@ -58,6 +58,7 @@ Keyword,Keyword "Letters, numbers, spaces or underscores only please","Letters, numbers, spaces or underscores only please" "Letters only please","Letters only please" "No white space please","No white space please" +"No marginal white space please","No marginal white space please" "Your ZIP-code must be in the range 902xx-xxxx to 905-xx-xxxx","Your ZIP-code must be in the range 902xx-xxxx to 905-xx-xxxx" "A positive or negative non-decimal number please","A positive or negative non-decimal number please" "The specified vehicle identification number (VIN) is invalid.","The specified vehicle identification number (VIN) is invalid." diff --git a/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js b/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js index fe312738469e7..ac1de4631e908 100644 --- a/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js +++ b/app/code/Magento/Ui/view/base/web/js/core/renderer/layout.js @@ -224,7 +224,7 @@ define([ */ build: function (parent, node, name) { var defaults = parent && parent.childDefaults || {}, - children = node.children, + children = this.filterDisabledChildren(node.children), type = getNodeType(parent, node), dataScope = getDataScope(parent, node), component, @@ -294,6 +294,35 @@ define([ return node; }, + /** + * Filter out all disabled components. + * + * @param {Object} children + * @returns {*} + */ + filterDisabledChildren: function (children) { + var cIds; + + //cleanup children config.componentDisabled = true + if (children && typeof children === 'object') { + cIds = Object.keys(children); + + if (cIds) { + _.each(cIds, function (cId) { + if (typeof children[cId] === 'object' && + children[cId].hasOwnProperty('config') && + typeof children[cId].config === 'object' && + children[cId].config.hasOwnProperty('componentDisabled') && + children[cId].config.componentDisabled === true) { + delete children[cId]; + } + }); + } + } + + return children; + }, + /** * Init component. * diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js index a4c56958911a8..be7a1a13fbd61 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/actions.js @@ -78,8 +78,8 @@ define([ }, /** - * Adds new action. If action with a specfied identifier - * already exists, than the original will be overrided. + * Adds new action. If an action with the specified identifier + * already exists, then the original will be overridden. * * @param {String} index - Actions' identifier. * @param {Object} action - Actions' data. @@ -108,7 +108,7 @@ define([ /** * Processes actions, setting additional information to them and - * evaluating ther properties as a string templates. + * evaluating their properties as string templates. * * @private * @param {Object} row - Row object. @@ -204,11 +204,11 @@ define([ }, /** - * Creates action callback based on its' data. If action doesn't spicify + * Creates action callback based on it's data. If the action doesn't specify * a callback function than the default one will be used. * * @private - * @param {Object} action - Actions' object. + * @param {Object} action - Action's object. * @returns {Function} Callback function. */ _getCallback: function (action) { @@ -234,7 +234,7 @@ define([ * Creates action callback for multiple actions. * * @private - * @param {Object} action - Actions' object. + * @param {Object} action - Action's object. * @returns {Function} Callback function. */ _getCallbacks: function (action) { @@ -259,12 +259,12 @@ define([ /** * Default action callback. Redirects to - * the specified in actions' data url. + * the specified in action's data url. * - * @param {String} actionIndex - Actions' identifier. - * @param {(Number|String)} recordId - Id of the record accociated - * with a specfied action. - * @param {Object} action - Actions' data. + * @param {String} actionIndex - Action's identifier. + * @param {(Number|String)} recordId - Id of the record associated + * with a specified action. + * @param {Object} action - Action's data. */ defaultCallback: function (actionIndex, recordId, action) { window.location.href = action.href; @@ -273,7 +273,7 @@ define([ /** * Shows actions' confirmation window. * - * @param {Object} action - Actions' data. + * @param {Object} action - Action's data. * @param {Function} callback - Callback that will be * invoked if action is confirmed. */ diff --git a/app/code/Magento/Ui/view/base/web/js/lib/registry/registry.js b/app/code/Magento/Ui/view/base/web/js/lib/registry/registry.js index 5c3c71e31823d..826e8ec8c33b4 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/registry/registry.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/registry/registry.js @@ -17,7 +17,7 @@ define([ var privateData = new WeakMap(); /** - * Extarcts private items storage associated + * Extracts private item storage associated * with a provided registry instance. * * @param {Object} container @@ -39,7 +39,7 @@ define([ } /** - * Wrapper function used for convinient access to the elements. + * Wrapper function used for convenient access to the elements. * See 'async' method for examples of usage and comparison * with a regular 'get' method. * @@ -139,7 +139,7 @@ define([ * which matches specified search criteria. * * @param {Object} data - Data object where to perform a lookup. - * @param {(String|Object|Function)} query - Seach criteria. + * @param {(String|Object|Function)} query - Search criteria. * @param {Boolean} findAll - Flag that defines whether to * search for all applicable items or to stop on a first found entry. * @returns {Array|Object|*} @@ -322,8 +322,8 @@ define([ /** * Creates a wrapper function over the provided search query - * in order to provide somehow more convinient access to the - * registrie's items. + * in order to provide somehow more convenient access to the + * registry's items. * * @param {(String|Object|Function)} query - Search criteria. * See 'get' method for the syntax examples. diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js index eea199176681c..8e59f9d290906 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js @@ -115,6 +115,12 @@ define([ }, $.mage.__('No white space please') ], + 'no-marginal-whitespace': [ + function (value) { + return !/^\s+|\s+$/i.test(value); + }, + $.mage.__('No marginal white space please') + ], 'zip-range': [ function (value) { return utils.isEmpty(value) || /^90[2-5]-\d{2}-\d{4}$/.test(value); diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js b/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js index e7b2444dad95c..f82d24e82736a 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/modal-component.js @@ -93,7 +93,7 @@ define([ * @returns {Object} Chainable. */ initModalEvents: function () { - this.options.keyEventHandlers.escapeKey = this.options.outerClickHandler = this[this.onCancel].bind(this); + this.options.keyEventHandlers.escapeKey = this[this.onCancel].bind(this); return this; }, diff --git a/app/code/Magento/Ui/view/base/web/js/modal/modal.js b/app/code/Magento/Ui/view/base/web/js/modal/modal.js index 147a18c08c54c..884d6f46defcd 100644 --- a/app/code/Magento/Ui/view/base/web/js/modal/modal.js +++ b/app/code/Magento/Ui/view/base/web/js/modal/modal.js @@ -424,8 +424,7 @@ define([ * Creates overlay, append it to wrapper, set previous click event on overlay. */ _createOverlay: function () { - var events, - outerClickHandler = this.options.outerClickHandler || this.closeModal; + var events; this.overlay = $('.' + this.options.overlayClass); @@ -437,7 +436,6 @@ define([ } events = $._data(this.overlay.get(0), 'events'); events ? this.prevOverlayHandler = events.click[0].handler : false; - this.options.clickableOverlay ? this.overlay.unbind().on('click', outerClickHandler) : false; }, /** diff --git a/app/code/Magento/Ups/Test/Mftf/composer.json b/app/code/Magento/Ups/Test/Mftf/composer.json deleted file mode 100644 index b4efd391504c2..0000000000000 --- a/app/code/Magento/Ups/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-ups", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-config": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Ups/composer.json b/app/code/Magento/Ups/composer.json index b93d79308b097..8e262c2a82d09 100644 --- a/app/code/Magento/Ups/composer.json +++ b/app/code/Magento/Ups/composer.json @@ -16,7 +16,7 @@ "magento/module-config": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteIndexPage.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteIndexPage.xml new file mode 100644 index 0000000000000..9524e41dc7c9e --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteIndexPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> + <page name="AdminUrlRewriteIndexPage" url="admin/url_rewrite/index/" area="admin" module="Magento_UrlRewrite"> + <section name="AdminUrlRewriteIndexSection"/> + </page> +</pages> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml new file mode 100644 index 0000000000000..455748a0da534 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> + <section name="AdminUrlRewriteIndexSection"> + <element name="requestPathFilter" type="input" selector="#urlrewriteGrid_filter_request_path"/> + <element name="requestPathColumnValue" type="text" selector="//*[@id='urlrewriteGrid']//tbody//td[@data-column='request_path' and normalize-space(.)='{{columnValue}}']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml new file mode 100644 index 0000000000000..363e6491a25f0 --- /dev/null +++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUrlRewritesForProductInAnchorCategoriesTest.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminUrlRewritesForProductInAnchorCategoriesTest"> + <annotations> + <features value="Url Rewrite"/> + <stories value="Url-rewrites for product in anchor categories"/> + <title value="Url-rewrites for product in anchor categories"/> + <description value="For a product with category that has parent anchor categories, the rewrites is created when the category/product is saved."/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-76098"/> + <group value="urlRewrite"/> + </annotations> + + <!-- Preconditions--> + <!-- Create 3 categories --> + <before> + <createData entity="SimpleSubCategory" stepKey="simpleSubCategory1"/> + <createData entity="SubCategoryWithParent" stepKey="simpleSubCategory2"> + <requiredEntity createDataKey="simpleSubCategory1"/> + </createData> + <createData entity="SubCategoryWithParent" stepKey="simpleSubCategory3"> + <requiredEntity createDataKey="simpleSubCategory2"/> + </createData> + <!-- Create Simple product 1 and assign it to Category 3 --> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="simpleSubCategory3"/> + </createData> + </before> + <after> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="simpleSubCategory1" stepKey="deletesimpleSubCategory1"/> + <actionGroup ref="logout" stepKey="logout"/> + </after> + <!-- Steps --> + <!-- 1. Log in to Admin --> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + + <!-- 2. Open Marketing - SEO & Search - URL Rewrites --> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="amOnUrlRewriteIndexPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="inputProductName"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeValue1"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeValue2"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$/$$simpleSubCategory2.custom_attributes[url_key]$$/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeValue3"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$/$$simpleSubCategory2.custom_attributes[url_key]$$/$$simpleSubCategory3.custom_attributes[url_key]$$/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeValue4"/> + + <!-- 3. Edit Category 1 for DEFAULT Store View: --> + <actionGroup ref="switchCategoryStoreView" stepKey="switchStoreView"> + <argument name="store" value="_defaultStore.name"/> + <argument name="catName" value="$$simpleSubCategory1.name$$"/> + </actionGroup> + <click selector="{{AdminCategorySEOSection.SectionHeader}}" stepKey="openSeoSection2"/> + <uncheckOption selector="{{AdminCategorySEOSection.urlKeyDefaultValueCheckbox}}" stepKey="uncheckRedirect2"/> + <fillField selector="{{AdminCategorySEOSection.UrlKeyInput}}" userInput="$$simpleSubCategory1.custom_attributes[url_key]$$-new" stepKey="changeURLKey"/> + <checkOption selector="{{AdminCategorySEOSection.urlKeyRedirectCheckbox}}" stepKey="checkUrlKeyRedirect"/> + + <!-- 4. Save Category 1 --> + <click selector="{{AdminCategoryMainActionsSection.SaveButton}}" stepKey="saveCategory"/> + <seeElement selector="{{AdminCategoryMessagesSection.SuccessMessage}}" stepKey="assertSuccessMessageAfterSaved"/> + + <!-- 5. Open Marketing - SEO & Search - URL Rewrites --> + <amOnPage url="{{AdminUrlRewriteIndexPage.url}}" stepKey="amOnUrlRewriteIndexPage2"/> + <waitForPageLoad stepKey="waitForPageLoad2"/> + <fillField selector="{{AdminUrlRewriteIndexSection.requestPathFilter}}" userInput="$$createSimpleProduct.custom_attributes[url_key]$$.html" stepKey="inputProductName2"/> + <click selector="{{AdminDataGridHeaderSection.applyFilters}}" stepKey="clickSearchButton2"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeInListValue1"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeInListValue2"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$/$$simpleSubCategory2.custom_attributes[url_key]$$/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeInListValue3"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$/$$simpleSubCategory2.custom_attributes[url_key]$$/$$simpleSubCategory3.custom_attributes[url_key]$$/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeInListValue4"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$-new/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeInListValue5"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$-new/$$simpleSubCategory2.custom_attributes[url_key]$$/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeInListValue6"/> + <seeElement selector="{{AdminUrlRewriteIndexSection.requestPathColumnValue('$$simpleSubCategory1.custom_attributes[url_key]$$-new/$$simpleSubCategory2.custom_attributes[url_key]$$/$$simpleSubCategory3.custom_attributes[url_key]$$/$$createSimpleProduct.custom_attributes[url_key]$$.html')}}" stepKey="seeInListValue7"/> + </test> +</tests> diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/composer.json b/app/code/Magento/UrlRewrite/Test/Mftf/composer.json deleted file mode 100644 index 866709e80be0b..0000000000000 --- a/app/code/Magento/UrlRewrite/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-url-rewrite", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog-url-rewrite": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-cms-url-rewrite": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/UrlRewrite/composer.json b/app/code/Magento/UrlRewrite/composer.json index 2be712bf1ce5d..8f8b819d1a62a 100644 --- a/app/code/Magento/UrlRewrite/composer.json +++ b/app/code/Magento/UrlRewrite/composer.json @@ -12,7 +12,7 @@ "magento/module-cms-url-rewrite": "100.2.*" }, "type": "magento2-module", - "version": "101.0.4", + "version": "101.0.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/User/Block/Buttons.php b/app/code/Magento/User/Block/Buttons.php index b411f1c0cf8cd..bb7375ae83277 100644 --- a/app/code/Magento/User/Block/Buttons.php +++ b/app/code/Magento/User/Block/Buttons.php @@ -53,7 +53,7 @@ protected function _prepareLayout() ['label' => __('Reset'), 'onclick' => 'window.location.reload()', 'class' => 'reset'] ); - if (intval($this->getRequest()->getParam('rid'))) { + if ((int)$this->getRequest()->getParam('rid')) { $this->getToolbar()->addChild( 'deleteButton', \Magento\Backend\Block\Widget\Button::class, @@ -113,7 +113,7 @@ public function getSaveButtonHtml() */ public function getDeleteButtonHtml() { - if (intval($this->getRequest()->getParam('rid')) == 0) { + if ((int)$this->getRequest()->getParam('rid') == 0) { return; } return $this->getChildHtml('deleteButton'); diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Save.php b/app/code/Magento/User/Controller/Adminhtml/User/Save.php index 3b37ce10c0151..d7d1c8b0e22a6 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Save.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Save.php @@ -6,6 +6,7 @@ namespace Magento\User\Controller\Adminhtml\User; use Magento\Framework\Exception\AuthenticationException; +use Magento\Framework\Exception\MailException; use Magento\Framework\Exception\State\UserLockedException; use Magento\Security\Model\SecurityCookie; @@ -84,17 +85,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 (MailException $exception) { + $this->messageManager->addErrorMessage($exception->getMessage()); } catch (\Magento\Framework\Exception\AuthenticationException $e) { $this->messageManager->addError(__('You have entered an invalid password for current user.')); $this->redirectToEdit($model, $data); diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index d711220441aaf..69f565cedfc9a 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -7,7 +7,9 @@ use Magento\Backend\App\Area\FrontNameResolver; use Magento\Backend\Model\Auth\Credential\StorageInterface; +use Magento\Framework\App\DeploymentConfig; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Exception\MailException; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Exception\AuthenticationException; use Magento\Framework\Serialize\Serializer\Json; @@ -123,6 +125,11 @@ class User extends AbstractModel implements StorageInterface, UserInterface */ private $serializer; + /** + * @var DeploymentConfig + */ + private $deploymentConfig; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -138,6 +145,7 @@ class User extends AbstractModel implements StorageInterface, UserInterface * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data * @param Json $serializer + * @param DeploymentConfig|null $deploymentConfig * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -154,7 +162,8 @@ 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 ) { $this->_encryptor = $encryptor; parent::__construct($context, $registry, $resource, $resourceCollection, $data); @@ -166,6 +175,8 @@ public function __construct( $this->_storeManager = $storeManager; $this->validationRules = $validationRules; $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class); + $this->deploymentConfig = $deploymentConfig + ?? ObjectManager::getInstance()->get(DeploymentConfig::class); } /** @@ -235,7 +246,7 @@ public function beforeSave() } if ($this->getIsActive() !== null) { - $data['is_active'] = intval($this->getIsActive()); + $data['is_active'] = (int)$this->getIsActive(); } $this->addData($data); @@ -397,6 +408,41 @@ public function roleUserExists() return is_array($result) && count($result) > 0 ? true : false; } + /** + * Send a notification to an admin. + * + * @param string $templateConfigId + * @param array $templateVars + * @param string|null $toEmail + * @param string|null $toName + * @throws MailException + * + * @return void + */ + private function sendNotification( + string $templateConfigId, + array $templateVars, + string $toEmail = null, + string $toName = null + ) { + $toEmail = $toEmail ?? $this->getEmail(); + $toName = $toName ?? $this->getName(); + $this->_transportBuilder + ->setTemplateIdentifier($this->_config->getValue($templateConfigId)) + ->setTemplateModel(\Magento\Email\Model\BackendTemplate::class) + ->setTemplateOptions([ + 'area' => FrontNameResolver::AREA_CODE, + 'store' => Store::DEFAULT_STORE_ID + ]) + ->setTemplateVars($templateVars) + ->setFrom( + $this->_config->getValue(self::XML_PATH_FORGOT_EMAIL_IDENTITY) + ) + ->addTo($toEmail, $toName) + ->getTransport() + ->sendMessage(); + } + /** * Send email with reset password confirmation link * @@ -404,16 +450,16 @@ public function roleUserExists() */ 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->sendNotification( + self::XML_PATH_FORGOT_EMAIL_TEMPLATE, + [ + 'user' => $this, + 'store' => $this->_storeManager->getStore( + Store::DEFAULT_STORE_ID + ) + ] + ); - $transport->sendMessage(); return $this; } @@ -429,19 +475,62 @@ public function sendPasswordResetNotificationEmail() return $this; } + /** + * Send notification about a new user created. + * + * @throws MailException + * @return void + */ + private function sendNewUserNotificationEmail() + { + $toEmails = []; + + $generalEmail = $this->_config->getValue( + 'trans_email/ident_general/email' + ); + if ($generalEmail) { + $toEmails[] = $generalEmail; + } + + if ($adminEmail = $this->deploymentConfig->get('user_admin_email')) { + $toEmails[] = $adminEmail; + } + + foreach ($toEmails as $toEmail) { + $this->sendNotification( + 'admin/emails/new_user_notification_template', + [ + 'user' => $this, + 'store' => $this->_storeManager->getStore( + Store::DEFAULT_STORE_ID + ) + ], + $toEmail, + 'Administrator' + ); + } + } + /** * Check changes and send notification emails * + * @throws MailException * @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')); + if ($this->isObjectNew()) { + //Notification about a new user + $this->sendNewUserNotificationEmail(); + } elseif ($changes = $this->createChangesDescriptionString()) { + if ($this->getEmail() != $this->getOrigData('email') + && $this->getOrigData('email') + ) { + $this->sendUserNotificationEmail( + $changes, + $this->getOrigData('email') + ); } $this->sendUserNotificationEmail($changes); } @@ -481,31 +570,24 @@ protected function createChangesDescriptionString() * * @param string $changes * @param string $email + * @throws MailException * @return $this * @since 100.1.0 */ protected function sendUserNotificationEmail($changes, $email = null) { - if ($email === null) { - $email = $this->getEmail(); - } - - $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(); + $this->sendNotification( + self::XML_PATH_USER_NOTIFICATION_TEMPLATE, + [ + 'user' => $this, + 'store' => $this->_storeManager->getStore( + Store::DEFAULT_STORE_ID + ), + 'changes' => $changes + ], + $email + ); - $transport->sendMessage(); return $this; } diff --git a/app/code/Magento/User/Test/Mftf/composer.json b/app/code/Magento/User/Test/Mftf/composer.json deleted file mode 100644 index 125cddc3990f2..0000000000000 --- a/app/code/Magento/User/Test/Mftf/composer.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "magento/functional-test-module-user", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-security": "100.0.0-dev", - "magento/functional-test-module-integration": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/User/Test/Unit/Model/UserTest.php b/app/code/Magento/User/Test/Unit/Model/UserTest.php index 5e0a9a14c2b16..3be67112d8802 100644 --- a/app/code/Magento/User/Test/Unit/Model/UserTest.php +++ b/app/code/Magento/User/Test/Unit/Model/UserTest.php @@ -17,54 +17,10 @@ class UserTest extends \PHPUnit\Framework\TestCase { /** @var \Magento\User\Model\User */ - protected $model; + 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 $storetMock; - - /** @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; + private $userDataMock; /** * Set required values @@ -76,307 +32,16 @@ protected function setUp() ->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->storetMock = $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); $this->model = $objectManagerHelper->getObject( \Magento\User\Model\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->storetMock, '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->storetMock); - - $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->storetMock]) - ->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->storetMock); - - $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, - 'You did not sign in correctly or your account is temporarily disabled.' - ); - $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, - 'You need more permissions to access this.' - ); - $this->model->verifyIdentity($password); - } - /** * @return void */ @@ -402,216 +67,6 @@ 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 */ @@ -656,157 +111,4 @@ public function testIsResetPasswordLinkTokenExpiredIsNotExpiredToken() $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, - __('Your account is temporarily disabled.') - ); - } - - if (!$verifyIdentityResult) { - $this->expectException( - \Magento\Framework\Exception\AuthenticationException::class, - __('You have entered an invalid password for current user.') - ); - } - - $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/composer.json b/app/code/Magento/User/composer.json index b9854f9587922..4ff87f2a704a3 100644 --- a/app/code/Magento/User/composer.json +++ b/app/code/Magento/User/composer.json @@ -12,7 +12,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "101.0.3", + "version": "101.0.4", "license": [ "OSL-3.0", "AFL-3.0" 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/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/Usps/Test/Mftf/composer.json b/app/code/Magento/Usps/Test/Mftf/composer.json deleted file mode 100644 index a528d3ab0b6ed..0000000000000 --- a/app/code/Magento/Usps/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-usps", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-shipping": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-config": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Usps/composer.json b/app/code/Magento/Usps/composer.json index 0d01448ca3fb1..dd4231457a774 100644 --- a/app/code/Magento/Usps/composer.json +++ b/app/code/Magento/Usps/composer.json @@ -15,7 +15,7 @@ "lib-libxml": "*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Variable/Test/Mftf/composer.json b/app/code/Magento/Variable/Test/Mftf/composer.json deleted file mode 100644 index ac15df2bde1fd..0000000000000 --- a/app/code/Magento/Variable/Test/Mftf/composer.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "name": "magento/functional-test-module-variable", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Variable/composer.json b/app/code/Magento/Variable/composer.json index 2875f74ef1974..473df42feae8b 100644 --- a/app/code/Magento/Variable/composer.json +++ b/app/code/Magento/Variable/composer.json @@ -9,7 +9,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Vault/Test/Mftf/composer.json b/app/code/Magento/Vault/Test/Mftf/composer.json deleted file mode 100644 index d37d306d0c405..0000000000000 --- a/app/code/Magento/Vault/Test/Mftf/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "magento/functional-test-module-vault", - "description": "", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-payment": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "proprietary" - ] -} diff --git a/app/code/Magento/Vault/composer.json b/app/code/Magento/Vault/composer.json index 5c6cff396bc33..b285718b22b50 100644 --- a/app/code/Magento/Vault/composer.json +++ b/app/code/Magento/Vault/composer.json @@ -12,7 +12,7 @@ "magento/module-quote": "101.0.*" }, "type": "magento2-module", - "version": "101.0.3", + "version": "101.0.4", "license": [ "proprietary" ], diff --git a/app/code/Magento/Version/Test/Mftf/composer.json b/app/code/Magento/Version/Test/Mftf/composer.json deleted file mode 100644 index 7ef7d43d98a69..0000000000000 --- a/app/code/Magento/Version/Test/Mftf/composer.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "name": "magento/functional-test-module-version", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Version/composer.json b/app/code/Magento/Version/composer.json index 7b6880b641d0b..7f015cacb728c 100644 --- a/app/code/Magento/Version/composer.json +++ b/app/code/Magento/Version/composer.json @@ -6,7 +6,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php b/app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php index 1d8ccc127cea9..d1a3c0c0f0864 100644 --- a/app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php +++ b/app/code/Magento/Webapi/Controller/Rest/ParamsOverrider.php @@ -197,7 +197,7 @@ private function isPropertyDeclaredInDataObject( $index = array_search($serviceMethodParamName, array_column($methodParams, 'name')); if ($index !== false) { $paramObjectType = $methodParams[$index][MethodsMap::METHOD_META_TYPE]; - $setter = 'set' . ucfirst(SimpleDataObjectConverter::snakeCaseToCamelCase($objectProperty)); + $setter = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($objectProperty); if (array_key_exists( $setter, $this->getMethodsMap()->getMethodsMap($paramObjectType) diff --git a/app/code/Magento/Webapi/Test/Mftf/composer.json b/app/code/Magento/Webapi/Test/Mftf/composer.json deleted file mode 100644 index b2ce11994145e..0000000000000 --- a/app/code/Magento/Webapi/Test/Mftf/composer.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "magento/functional-test-module-webapi", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-authorization": "100.0.0-dev", - "magento/functional-test-module-integration": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "suggest": { - "magento/functional-test-module-user": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Webapi/composer.json b/app/code/Magento/Webapi/composer.json index ca1266b038666..8a0dd448c22e0 100644 --- a/app/code/Magento/Webapi/composer.json +++ b/app/code/Magento/Webapi/composer.json @@ -14,7 +14,7 @@ "magento/module-customer": "101.0.*" }, "type": "magento2-module", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/WebapiSecurity/Test/Mftf/composer.json b/app/code/Magento/WebapiSecurity/Test/Mftf/composer.json deleted file mode 100644 index e5a547c21cfe0..0000000000000 --- a/app/code/Magento/WebapiSecurity/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-webapi-security", - "description": "WebapiSecurity module provides option to loosen security on some webapi resources.", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-webapi": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/WebapiSecurity/composer.json b/app/code/Magento/WebapiSecurity/composer.json index fcc66a64a3f1d..24647ea1f980f 100644 --- a/app/code/Magento/WebapiSecurity/composer.json +++ b/app/code/Magento/WebapiSecurity/composer.json @@ -7,7 +7,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Weee/Plugin/Catalog/Controller/Adminhtml/Product/Initialization/Helper/ProcessTaxAttribute.php b/app/code/Magento/Weee/Plugin/Catalog/Controller/Adminhtml/Product/Initialization/Helper/ProcessTaxAttribute.php new file mode 100644 index 0000000000000..011a1b3f9766b --- /dev/null +++ b/app/code/Magento/Weee/Plugin/Catalog/Controller/Adminhtml/Product/Initialization/Helper/ProcessTaxAttribute.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Weee\Plugin\Catalog\Controller\Adminhtml\Product\Initialization\Helper; + +use Magento\Catalog\Model\Product; +use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; +use Magento\Framework\App\RequestInterface; + +/** + * Handles product tax attributes data initialization. + */ +class ProcessTaxAttribute +{ + /** + * @var RequestInterface + */ + private $request; + + /** + * @param RequestInterface $request + */ + public function __construct(RequestInterface $request) + { + $this->request = $request; + } + + /** + * @param Helper $subject + * @param Product $result + * @param Product $product + * @param array $productData + * @return Product + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterInitializeFromData( + Helper $subject, + Product $result, + Product $product, + array $productData + ): Product { + $attributes = $result->getAttributes(); + if (!empty($attributes)) { + foreach ($attributes as $attribute) { + if ($attribute->getFrontendInput() == 'weee' && !isset($productData[$attribute->getAttributeCode()])) { + $result->setData($attribute->getAttributeCode(), []); + } + } + } + + return $result; + } +} diff --git a/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml b/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml index 40a9a97f31425..c38d2e1945e80 100644 --- a/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml +++ b/app/code/Magento/Weee/Test/Mftf/Section/AdminProductAddFPTValueSection.xml @@ -10,6 +10,7 @@ xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> <section name="AdminProductAddFPTValueSection"> <element name="addFPT" type="button" selector="[data-index='{{FPTAttributeCode}}'] [data-action='add_new_row']" parameterized="true"/> + <element name="removeRowByIndex" type="button" selector="[data-index='{{FPTAttributeCode}}'] [data-action='remove_row']:nth-of-type({{rowIndex}})" parameterized="true"/> <element name="selectCountryForFPT" type="select" selector="(//select[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[country]')])[last()]" parameterized="true"/> <element name="selectStateForFPT" type="select" selector="(//select[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[state]')])[last()]" parameterized="true"/> <element name="setTaxValueForFPT" type="text" selector="(//input[contains(@name, 'product[{{FPTAttributeCode}}]') and contains(@name, '[value]')])[last()]" parameterized="true"/> diff --git a/app/code/Magento/Weee/Test/Mftf/Section/CheckoutCartSummarySection.xml b/app/code/Magento/Weee/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml similarity index 90% rename from app/code/Magento/Weee/Test/Mftf/Section/CheckoutCartSummarySection.xml rename to app/code/Magento/Weee/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml index 9b6541b93541d..45277a7956f3d 100644 --- a/app/code/Magento/Weee/Test/Mftf/Section/CheckoutCartSummarySection.xml +++ b/app/code/Magento/Weee/Test/Mftf/Section/StorefrontCheckoutCartSummarySection.xml @@ -8,7 +8,7 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../dev/tests/acceptance/vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - <section name="CheckoutCartSummarySection"> + <section name="StorefrontCheckoutCartSummarySection"> <element name="amountFPT" type="text" selector=".totals td[data-th='FPT'] .price"/> </section> </sections> diff --git a/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml b/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml new file mode 100644 index 0000000000000..cccac1395b4e9 --- /dev/null +++ b/app/code/Magento/Weee/Test/Mftf/Test/AdminRemoveProductWeeeAttributeOptionTest.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> + <test name="AdminRemoveProductWeeeAttributeOptionTest"> + <annotations> + <features value="Weee attribute options can be removed in product page"/> + <title value="Weee attribute options can be removed in product page"/> + <description value="Weee attribute options can be removed in product page"/> + <severity value="CRITICAL"/> + <testCaseId value="MAGETWO-94817"/> + <group value="weee"/> + </annotations> + <before> + <createData entity="productFPTAttribute" stepKey="createProductFPTAttribute"/> + <createData entity="AddToDefaultSet" stepKey="addFPTToAttributeSet"> + <requiredEntity createDataKey="createProductFPTAttribute"/> + </createData> + <createData entity="SimpleOne" stepKey="createSimpleProduct"/> + <actionGroup ref="LoginAsAdmin" stepKey="loginAsAdmin"/> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProductInitial"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProductInitial"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="AdminProductAddFPTValueActionGroup" stepKey="addWeeeAttributeValue"> + <argument name="FPTAttributeCode" value="$$createProductFPTAttribute.attribute_code$$"/> + <argument name="stateForFPT" value="California"/> + <argument name="valueForFPT" value="10"/> + </actionGroup> + <actionGroup ref="SaveProductOnProductPageOnAdmin" stepKey="saveProductInitial"/> + </before> + <after> + <amOnPage url="{{AdminProductIndexPage.url}}" stepKey="navigateToProductListing"/> + <waitForPageLoad stepKey="waitForProductListingPageLoad"/> + <actionGroup ref="AdminResetProductGridToDefaultViewActionGroup" stepKey="resetGridToDefaultKeywordSearch"/> + <actionGroup ref="logout" stepKey="logout"/> + <deleteData createDataKey="createProductFPTAttribute" stepKey="deleteProductFPTAttribute"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + </after> + <!-- Test Steps --> + <!-- Step 1: Open created product edit page --> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchForSimpleProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + <!-- Step 2: Remove weee attribute options --> + <scrollTo stepKey="scrollToProductDetails" selector="[data-index='product-details']" x="0" y="-80" /> + <click selector="{{AdminProductAddFPTValueSection.removeRowByIndex('$$createProductFPTAttribute.attribute_code$$','1')}}" stepKey="removeAttributeOption"/> + <actionGroup ref="SaveProductOnProductPageOnAdmin" stepKey="saveProduct"/> + <!-- Assert weee attribute options are empty --> + <dontSeeElement selector="{{AdminProductAddFPTValueSection.removeRowByIndex('$$createProductFPTAttribute.attribute_code$$','1')}}" stepKey="dontSeeOptions"/> + </test> +</tests> diff --git a/app/code/Magento/Weee/Test/Mftf/composer.json b/app/code/Magento/Weee/Test/Mftf/composer.json deleted file mode 100644 index 7da445d3f1826..0000000000000 --- a/app/code/Magento/Weee/Test/Mftf/composer.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "magento/functional-test-module-weee", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-tax": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-directory": "100.0.0-dev", - "magento/functional-test-module-eav": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-page-cache": "100.0.0-dev", - "magento/functional-test-module-quote": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-bundle": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Weee.php b/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Weee.php index c96a7a0200334..87dea65a41dcf 100644 --- a/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Weee.php +++ b/app/code/Magento/Weee/Ui/DataProvider/Product/Form/Modifier/Weee.php @@ -180,6 +180,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'component' => 'Magento_Weee/js/fpt-group', 'visible' => true, 'label' => __('Country/State'), + 'showLabel' => false, ], ], ], @@ -197,6 +198,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'validation' => [ 'required-entry' => true, ], + 'showLabel' => false, ], ], ], @@ -216,6 +218,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) ], 'caption' => '*', 'visible' => true, + 'showLabel' => false, ], ], ], @@ -233,6 +236,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'validation' => [ 'validate-fpt-group' => true ], + 'showLabel' => false, ], ], ], @@ -252,6 +256,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'validation' => [ 'required-entry' => true ], + 'showLabel' => false, ], ], ], @@ -267,6 +272,7 @@ protected function modifyAttributeConfig($attributeCode, array $attributeConfig) 'label' => __('Website'), 'visible' => $this->websiteManager->isMultiWebsites(), 'options' => $this->websiteManager->getWebsites($product, $eavAttribute), + 'showLabel' => false, ], ], ], diff --git a/app/code/Magento/Weee/composer.json b/app/code/Magento/Weee/composer.json index 1c9ba96d00df1..206af1f6d150a 100644 --- a/app/code/Magento/Weee/composer.json +++ b/app/code/Magento/Weee/composer.json @@ -21,7 +21,7 @@ "magento/module-bundle": "100.2.*" }, "type": "magento2-module", - "version": "100.2.2", + "version": "100.2.3", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Weee/etc/di.xml b/app/code/Magento/Weee/etc/di.xml index e52aebd3af8b5..8b433163cad22 100644 --- a/app/code/Magento/Weee/etc/di.xml +++ b/app/code/Magento/Weee/etc/di.xml @@ -78,4 +78,7 @@ </argument> </arguments> </type> + <type name="Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper"> + <plugin name="weeeAttributeOptionsProcess" type="Magento\Weee\Plugin\Catalog\Controller\Adminhtml\Product\Initialization\Helper\ProcessTaxAttribute"/> + </type> </config> diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php index d813e94437326..2c5498a39eae0 100644 --- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php +++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Chooser.php @@ -180,7 +180,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/code/Magento/Widget/Model/Widget/Instance.php b/app/code/Magento/Widget/Model/Widget/Instance.php index afe7ef3766f7c..1ccb75ad6611a 100644 --- a/app/code/Magento/Widget/Model/Widget/Instance.php +++ b/app/code/Magento/Widget/Model/Widget/Instance.php @@ -15,7 +15,7 @@ * @method string getTitle() * @method \Magento\Widget\Model\Widget\Instance setTitle(string $value) * @method \Magento\Widget\Model\Widget\Instance setStoreIds(string $value) - * @method \Magento\Widget\Model\Widget\Instance setWidgetParameters(string $value) + * @method \Magento\Widget\Model\Widget\Instance setWidgetParameters(string|array $value) * @method int getSortOrder() * @method \Magento\Widget\Model\Widget\Instance setSortOrder(int $value) * @method \Magento\Widget\Model\Widget\Instance setThemeId(int $value) diff --git a/app/code/Magento/Widget/Test/Mftf/composer.json b/app/code/Magento/Widget/Test/Mftf/composer.json deleted file mode 100644 index 529bcffe45e0e..0000000000000 --- a/app/code/Magento/Widget/Test/Mftf/composer.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "name": "magento/functional-test-module-widget", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-cms": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-email": "100.0.0-dev", - "magento/functional-test-module-theme": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-variable": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-widget-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Widget/composer.json b/app/code/Magento/Widget/composer.json index 7e0a3f21cf458..23ee88072fbd5 100644 --- a/app/code/Magento/Widget/composer.json +++ b/app/code/Magento/Widget/composer.json @@ -16,7 +16,7 @@ "magento/module-widget-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "101.0.3", + "version": "101.0.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml b/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml index a0a0fc040a262..3441cf6b5d52a 100644 --- a/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml +++ b/app/code/Magento/Widget/view/adminhtml/templates/instance/edit/layout.phtml @@ -304,11 +304,6 @@ var WidgetInstance = { }, displayPageGroup : function(container, additional) { container = $(container); - if (!container) { -// if (activePageGroupId = this.activePageGroups.get(container.up('div.page_group_container').id)) { -// this.hideBlockContainer(activePageGroupId); -// } - } if (!additional) { additional = {}; } diff --git a/app/code/Magento/Wishlist/Controller/Index/DownloadCustomOption.php b/app/code/Magento/Wishlist/Controller/Index/DownloadCustomOption.php index e43a03e72d87c..095e64ddf7620 100644 --- a/app/code/Magento/Wishlist/Controller/Index/DownloadCustomOption.php +++ b/app/code/Magento/Wishlist/Controller/Index/DownloadCustomOption.php @@ -92,7 +92,8 @@ public function execute() $this->_fileResponseFactory->create( $info['title'], ['value' => $info['quote_path'], 'type' => 'filename'], - DirectoryList::ROOT + DirectoryList::ROOT, + $info['type'] ); } } catch (\Exception $e) { diff --git a/app/code/Magento/Wishlist/Helper/Rss.php b/app/code/Magento/Wishlist/Helper/Rss.php index 14ca52e92ad84..15f8046931fbc 100644 --- a/app/code/Magento/Wishlist/Helper/Rss.php +++ b/app/code/Magento/Wishlist/Helper/Rss.php @@ -103,8 +103,8 @@ public function getCustomer() { if ($this->_customer === null) { $params = $this->urlDecoder->decode($this->_getRequest()->getParam('data')); - $data = explode(',', $params); - $customerId = abs(intval($data[0])); + $data = explode(',', $params); + $customerId = abs((int)$data[0]); if ($customerId && ($customerId == $this->_customerSession->getCustomerId())) { $this->_customer = $this->_customerRepository->getById($customerId); } else { diff --git a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection.php b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection.php index d3b710f229527..3b0db3bb7e2db 100644 --- a/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection.php +++ b/app/code/Magento/Wishlist/Model/ResourceModel/Item/Collection.php @@ -479,7 +479,7 @@ public function addDaysFilter($constraints) if (isset($constraints['to'])) { $firstDay = new \DateTime(); - $firstDay->modify('-' . $gmtOffset . ' second')->modify('-' . (intval($constraints['to']) + 1) . ' day'); + $firstDay->modify('-' . $gmtOffset . ' second')->modify('-' . ((int)$constraints['to'] + 1) . ' day'); $filter['from'] = $firstDay; } diff --git a/app/code/Magento/Wishlist/Setup/UpgradeSchema.php b/app/code/Magento/Wishlist/Setup/UpgradeSchema.php new file mode 100644 index 0000000000000..20297da771a8e --- /dev/null +++ b/app/code/Magento/Wishlist/Setup/UpgradeSchema.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Wishlist\Setup; + +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; +use Magento\Framework\Setup\UpgradeSchemaInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; + +/** + * Upgrade Schema + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * @inheritdoc + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + $connection = $setup->getConnection(); + if (version_compare($context->getVersion(), '2.0.2', '<')) { + $connection->addForeignKey( + $connection->getForeignKeyName( + $setup->getTable('wishlist_item_option'), + 'product_id', + $setup->getTable('catalog_product_entity'), + 'entity_id' + ), + $setup->getTable('wishlist_item_option'), + 'product_id', + $setup->getTable('catalog_product_entity'), + 'entity_id', + AdapterInterface::FK_ACTION_CASCADE, + true + ); + } + $setup->endSetup(); + } +} diff --git a/app/code/Magento/Wishlist/Test/Mftf/composer.json b/app/code/Magento/Wishlist/Test/Mftf/composer.json deleted file mode 100644 index 942681c8c3959..0000000000000 --- a/app/code/Magento/Wishlist/Test/Mftf/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "magento/functional-test-module-wishlist", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/functional-test-module-store": "100.0.0-dev", - "magento/functional-test-module-customer": "100.0.0-dev", - "magento/functional-test-module-catalog": "100.0.0-dev", - "magento/functional-test-module-checkout": "100.0.0-dev", - "magento/functional-test-module-catalog-inventory": "100.0.0-dev", - "magento/functional-test-module-rss": "100.0.0-dev", - "magento/functional-test-module-backend": "100.0.0-dev", - "magento/functional-test-module-sales": "100.0.0-dev", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-ui": "100.0.0-dev" - }, - "suggest": { - "magento/functional-test-module-configurable-product": "100.0.0-dev", - "magento/functional-test-module-downloadable": "100.0.0-dev", - "magento/functional-test-module-bundle": "100.0.0-dev", - "magento/functional-test-module-cookie": "100.0.0-dev", - "magento/functional-test-module-grouped-product": "100.0.0-dev", - "magento/functional-test-module-wishlist-sample-data": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/DownloadCustomOptionTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/DownloadCustomOptionTest.php deleted file mode 100644 index 6fefe18cf8975..0000000000000 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/DownloadCustomOptionTest.php +++ /dev/null @@ -1,154 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Wishlist\Test\Unit\Controller\Index; - -class DownloadCustomOptionTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var \Magento\Wishlist\Controller\Index\DownloadCustomOption - */ - protected $model; - - /** - * @var \Magento\Framework\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Framework\App\Response\Http\FileFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $fileResponseFactoryMock; - - /** - * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $requestMock; - - /** - * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $objectManagerMock; - - /** - * @var \Magento\Framework\Controller\ResultFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultFactoryMock; - - /** - * @var \Magento\Framework\Serialize\Serializer\Json|\PHPUnit_Framework_MockObject_MockObject - */ - protected $jsonMock; - - protected function setUp() - { - $this->fileResponseFactoryMock = $this->getMockBuilder(\Magento\Framework\App\Response\Http\FileFactory::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->jsonMock = $this->getMockBuilder(\Magento\Framework\Serialize\Serializer\Json::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) - ->disableOriginalConstructor() - ->setMethods(['create', 'get', 'configure']) - ->getMock(); - - $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->contextMock = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class) - ->disableOriginalConstructor() - ->getMock(); - $this->contextMock->expects($this->any()) - ->method('getObjectManager') - ->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->any()) - ->method('getRequest') - ->willReturn($this->requestMock); - $this->contextMock->expects($this->any()) - ->method('getResultFactory') - ->willReturn($this->resultFactoryMock); - - $this->model = new \Magento\Wishlist\Controller\Index\DownloadCustomOption( - $this->contextMock, - $this->fileResponseFactoryMock, - $this->jsonMock - ); - } - - public function testExecute() - { - $data = [ - 'number' => 42, - 'string' => 'string_value', - 'boolean' => true, - 'collection' => [1, 2, 3], - 'secret_key' => 999 - ]; - $serialized_data = json_encode($data); - - $optionMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item\Option::class) - ->disableOriginalConstructor() - ->setMethods(['getProductId', 'load', 'getId', 'getValue']) - ->getMock(); - $optionMock->expects($this->any()) - ->method('load') - ->willReturnSelf(); - $optionMock->expects($this->any()) - ->method('getId') - ->willReturn(true); - $optionMock->expects($this->any()) - ->method('getProductId') - ->willReturn('some_value'); - $optionMock->expects($this->any()) - ->method('getValue') - ->willReturn($serialized_data); - - $productOptionMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Option::class) - ->disableOriginalConstructor() - ->setMethods(['getProductId', 'load', 'getId', 'getType']) - ->getMock(); - $productOptionMock->expects($this->any()) - ->method('load') - ->willReturnSelf(); - $productOptionMock->expects($this->any()) - ->method('getId') - ->willReturn(true); - $productOptionMock->expects($this->any()) - ->method('getProductId') - ->willReturn('some_value'); - $productOptionMock->expects($this->any()) - ->method('getType') - ->willReturn('file'); - - $this->objectManagerMock->expects($this->any()) - ->method('create') - ->willReturnMap( - [ - [\Magento\Wishlist\Model\Item\Option::class, [], $optionMock], - [\Magento\Catalog\Model\Product\Option::class, [], $productOptionMock] - ] - ); - - $this->requestMock->expects($this->any()) - ->method('getParam') - ->willReturn(1); - - $this->jsonMock->expects($this->once()) - ->method('unserialize') - ->willReturnCallback(function ($value) { - return json_decode($value, true); - }); - - $this->assertEquals(null, $this->model->execute()); - } -} diff --git a/app/code/Magento/Wishlist/composer.json b/app/code/Magento/Wishlist/composer.json index 18ef1c42d24d1..4db0e55d869fc 100644 --- a/app/code/Magento/Wishlist/composer.json +++ b/app/code/Magento/Wishlist/composer.json @@ -23,7 +23,7 @@ "magento/module-wishlist-sample-data": "Sample Data version:100.2.*" }, "type": "magento2-module", - "version": "101.0.3", + "version": "101.0.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/code/Magento/Wishlist/etc/module.xml b/app/code/Magento/Wishlist/etc/module.xml index e7626f504e1f1..ade606be9e086 100644 --- a/app/code/Magento/Wishlist/etc/module.xml +++ b/app/code/Magento/Wishlist/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Wishlist" setup_version="2.0.1"> + <module name="Magento_Wishlist" setup_version="2.0.2"> <sequence> <module name="Magento_Customer"/> <module name="Magento_Catalog"/> diff --git a/app/code/Magento/WishlistAnalytics/Test/Mftf/composer.json b/app/code/Magento/WishlistAnalytics/Test/Mftf/composer.json deleted file mode 100644 index f30c8d08a3a8e..0000000000000 --- a/app/code/Magento/WishlistAnalytics/Test/Mftf/composer.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "magento/functional-test-module-wishlist-analytics", - "description": "N/A", - "config": { - "sort-packages": true - }, - "require": { - "php": "~7.0.13|~7.1.0", - "magento/magento2-functional-testing-framework": "2.2.0", - "magento/functional-test-module-wishlist": "100.0.0-dev" - }, - "type": "magento2-test", - "license": [ - "OSL-3.0", - "AFL-3.0" - ] -} diff --git a/app/code/Magento/WishlistAnalytics/composer.json b/app/code/Magento/WishlistAnalytics/composer.json index af3e114ca8167..820430f6c7d17 100644 --- a/app/code/Magento/WishlistAnalytics/composer.json +++ b/app/code/Magento/WishlistAnalytics/composer.json @@ -7,7 +7,7 @@ "magento/module-wishlist": "101.0.*" }, "type": "magento2-module", - "version": "100.2.1", + "version": "100.2.2", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_footer.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_footer.less index 2958708e51deb..0244775fad0b6 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_footer.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/_footer.less @@ -25,7 +25,7 @@ border-top: @footer__border-width solid @footer__border-color; color: @footer__color; margin-top: auto; - padding: 2.6rem 2rem 6rem 3rem; + padding: 2.6rem 3rem 6rem; a { .lib-link( diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_search.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_search.less index 3265c8b2fc387..8fcb0f94fff4e 100644 --- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_search.less +++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/actions-group/_search.less @@ -151,7 +151,7 @@ background-color: transparent; border: 1px solid transparent; font-size: @search-global-input__font-size; - height: @search-global-input__height; + height: @search-global-input__height + .2; padding: @search-global-input__padding-top @search-global-input__padding-side @search-global-input__padding-bottom; position: absolute; right: 0; diff --git a/app/design/adminhtml/Magento/backend/composer.json b/app/design/adminhtml/Magento/backend/composer.json index fc6034c74dfae..11f13279b8bc8 100644 --- a/app/design/adminhtml/Magento/backend/composer.json +++ b/app/design/adminhtml/Magento/backend/composer.json @@ -6,7 +6,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-theme", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_calendar-temp.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_calendar-temp.less index 416fb2a3071a5..032df8cf4373a 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_calendar-temp.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_calendar-temp.less @@ -43,7 +43,7 @@ height: @action__height; margin-left: -@action__height; overflow: hidden; - position: relative; + position: absolute; vertical-align: top; z-index: 1; diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_extends.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_extends.less index 63f78940d4046..01b5ae080130f 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_extends.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_extends.less @@ -54,11 +54,9 @@ &._required { > .admin__field-label { span { - padding-left: 1.5rem; - &:after { left: 0; - margin-left: @temp_gutter; // @todo ui: update after finalizing Form Grid mixing css/source/forms/_temp.less:10 + margin-left: @temp_gutter; } } } diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less index 6dbef05ea2389..8107c81030590 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less @@ -59,7 +59,11 @@ } .abs-field-no-label { + /** + * @codingStandardsIgnoreStart + */ #mix-grid .return_length(@field-label-grid__column, @field-grid__columns, '+'); + //@codingStandardsIgnoreEnd margin-left: @_length; } @@ -166,6 +170,13 @@ .admin__control-text, .admin__control-textarea { width: 100%; + &.disabled { + background-color: #e9e9e9; + border-color: #adadad; + color: #303030; + cursor: not-allowed; + opacity: .5; + } } } diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less index 0a463a95e3182..158cb0ebc0ed1 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_shipping.less @@ -98,11 +98,6 @@ text-align: center; top: 0; } - - .action-select-shipping-item { - &:extend(.abs-no-display-s all); - visibility: hidden; - } } } diff --git a/app/design/frontend/Magento/blank/composer.json b/app/design/frontend/Magento/blank/composer.json index 13699e87240f6..5219254e42674 100644 --- a/app/design/frontend/Magento/blank/composer.json +++ b/app/design/frontend/Magento/blank/composer.json @@ -6,7 +6,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-theme", - "version": "100.2.3", + "version": "100.2.4", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/app/design/frontend/Magento/luma/Magento_Rma/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Rma/web/css/source/_module.less index 7c96827c5f6b2..e8adcc2f0e4f3 100644 --- a/app/design/frontend/Magento/luma/Magento_Rma/web/css/source/_module.less +++ b/app/design/frontend/Magento/luma/Magento_Rma/web/css/source/_module.less @@ -40,6 +40,14 @@ &:extend(.abs-status all); } + .table-wrapper.table-returns { + .returns-details { + &.hidden { + display: none; + } + } + } + .block-returns-comments { .returns-comments { dt, diff --git a/app/design/frontend/Magento/luma/composer.json b/app/design/frontend/Magento/luma/composer.json index 4bd2704ac1dff..19d37d0216064 100644 --- a/app/design/frontend/Magento/luma/composer.json +++ b/app/design/frontend/Magento/luma/composer.json @@ -7,7 +7,7 @@ "magento/framework": "101.0.*" }, "type": "magento2-theme", - "version": "100.2.4", + "version": "100.2.5", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/composer.json b/composer.json index 82a49134fae7d..5a35f7c94a209 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/magento2ce", "description": "Magento 2 (Open Source)", "type": "project", - "version": "2.2.6-dev", + "version": "2.2.7-dev", "license": [ "OSL-3.0", "AFL-3.0" @@ -34,6 +34,7 @@ "zendframework/zend-db": "^2.8.2", "zendframework/zend-captcha": "^2.7.1", "zendframework/zend-session": "^2.7.3", + "zendframework/zend-mail": "^2.9.0", "magento/zendframework1": "~1.13.0", "colinmollenhour/credis": "1.8.2", "colinmollenhour/php-redis-session-abstract": "1.3.4", @@ -73,6 +74,7 @@ "ramsey/uuid": "~3.7.3" }, "require-dev": { + "magento/magento2-functional-testing-framework": "2.3.6", "phpunit/phpunit": "~6.2.0", "squizlabs/php_codesniffer": "3.2.2", "phpmd/phpmd": "@stable", @@ -85,125 +87,125 @@ "ext-pcntl": "Need for run processes in parallel mode" }, "replace": { - "magento/module-marketplace": "100.2.2", - "magento/module-admin-notification": "100.2.3", - "magento/module-advanced-pricing-import-export": "100.2.3", - "magento/module-analytics": "100.2.2", - "magento/module-authorization": "100.2.1", - "magento/module-authorizenet": "100.2.1", - "magento/module-backend": "100.2.5", - "magento/module-backup": "100.2.4", - "magento/module-braintree": "100.2.5", - "magento/module-bundle": "100.2.4", - "magento/module-bundle-import-export": "100.2.2", - "magento/module-cache-invalidate": "100.2.1", - "magento/module-captcha": "100.2.2", - "magento/module-catalog": "102.0.5", - "magento/module-catalog-analytics": "100.2.1", - "magento/module-catalog-import-export": "100.2.4", - "magento/module-catalog-inventory": "100.2.4", - "magento/module-catalog-rule": "101.0.4", - "magento/module-catalog-rule-configurable": "100.2.1", - "magento/module-catalog-search": "100.2.4", - "magento/module-catalog-url-rewrite": "100.2.4", - "magento/module-catalog-widget": "100.2.2", - "magento/module-checkout": "100.2.5", - "magento/module-checkout-agreements": "100.2.1", - "magento/module-cms": "102.0.5", - "magento/module-cms-url-rewrite": "100.2.1", - "magento/module-config": "101.0.5", - "magento/module-configurable-import-export": "100.2.2", - "magento/module-configurable-product": "100.2.5", - "magento/module-configurable-product-sales": "100.2.2", - "magento/module-contact": "100.2.2", - "magento/module-cookie": "100.2.1", - "magento/module-cron": "100.2.3", - "magento/module-currency-symbol": "100.2.1", - "magento/module-customer": "101.0.5", - "magento/module-customer-analytics": "100.2.1", - "magento/module-customer-import-export": "100.2.3", - "magento/module-deploy": "100.2.4", - "magento/module-developer": "100.2.3", - "magento/module-dhl": "100.2.2", - "magento/module-directory": "100.2.4", - "magento/module-downloadable": "100.2.4", - "magento/module-downloadable-import-export": "100.2.1", - "magento/module-eav": "101.0.4", - "magento/module-email": "100.2.3", - "magento/module-encryption-key": "100.2.1", - "magento/module-fedex": "100.2.2", - "magento/module-gift-message": "100.2.1", - "magento/module-google-adwords": "100.2.1", - "magento/module-google-analytics": "100.2.3", - "magento/module-google-optimizer": "100.2.2", - "magento/module-grouped-import-export": "100.2.1", - "magento/module-grouped-product": "100.2.3", - "magento/module-import-export": "100.2.5", - "magento/module-indexer": "100.2.3", - "magento/module-instant-purchase": "100.2.1", - "magento/module-integration": "100.2.3", - "magento/module-layered-navigation": "100.2.2", - "magento/module-media-storage": "100.2.1", - "magento/module-msrp": "100.2.1", - "magento/module-multishipping": "100.2.2", - "magento/module-new-relic-reporting": "100.2.3", - "magento/module-newsletter": "100.2.4", - "magento/module-offline-payments": "100.2.1", - "magento/module-offline-shipping": "100.2.3", - "magento/module-page-cache": "100.2.2", - "magento/module-payment": "100.2.3", - "magento/module-paypal": "100.2.3", - "magento/module-persistent": "100.2.1", - "magento/module-product-alert": "100.2.2", - "magento/module-product-video": "100.2.3", - "magento/module-quote": "101.0.4", - "magento/module-quote-analytics": "100.2.1", - "magento/module-release-notification": "100.2.2", - "magento/module-reports": "100.2.5", - "magento/module-require-js": "100.2.2", - "magento/module-review": "100.2.5", - "magento/module-review-analytics": "100.2.1", - "magento/module-robots": "100.2.2", - "magento/module-rss": "100.2.1", - "magento/module-rule": "100.2.2", - "magento/module-sales": "101.0.4", - "magento/module-sales-analytics": "100.2.1", - "magento/module-sales-inventory": "100.2.1", - "magento/module-sales-rule": "101.0.3", - "magento/module-sales-sequence": "100.2.1", - "magento/module-sample-data": "100.2.3", - "magento/module-search": "100.2.4", - "magento/module-security": "100.2.2", - "magento/module-send-friend": "100.2.1", - "magento/module-shipping": "100.2.5", - "magento/module-signifyd": "100.2.2", - "magento/module-sitemap": "100.2.4", - "magento/module-store": "100.2.4", - "magento/module-swagger-webapi": "100.0.0", - "magento/module-swagger": "100.2.3", - "magento/module-swatches": "100.2.3", - "magento/module-swatches-layered-navigation": "100.2.1", - "magento/module-tax": "100.2.5", - "magento/module-tax-import-export": "100.2.1", - "magento/module-theme": "100.2.5", - "magento/module-translation": "100.2.4", - "magento/module-ui": "101.0.5", - "magento/module-ups": "100.2.3", - "magento/module-url-rewrite": "101.0.4", - "magento/module-user": "101.0.3", - "magento/module-usps": "100.2.3", - "magento/module-variable": "100.2.4", - "magento/module-vault": "101.0.3", - "magento/module-version": "100.2.1", - "magento/module-webapi": "100.2.3", - "magento/module-webapi-security": "100.2.2", - "magento/module-weee": "100.2.2", - "magento/module-widget": "101.0.3", - "magento/module-wishlist": "101.0.3", - "magento/module-wishlist-analytics": "100.2.1", - "magento/theme-adminhtml-backend": "100.2.3", - "magento/theme-frontend-blank": "100.2.3", - "magento/theme-frontend-luma": "100.2.4", + "magento/module-marketplace": "100.2.3", + "magento/module-admin-notification": "100.2.4", + "magento/module-advanced-pricing-import-export": "100.2.4", + "magento/module-analytics": "100.2.3", + "magento/module-authorization": "100.2.2", + "magento/module-authorizenet": "100.2.2", + "magento/module-backend": "100.2.6", + "magento/module-backup": "100.2.5", + "magento/module-braintree": "100.2.6", + "magento/module-bundle": "100.2.5", + "magento/module-bundle-import-export": "100.2.3", + "magento/module-cache-invalidate": "100.2.2", + "magento/module-captcha": "100.2.3", + "magento/module-catalog": "102.0.6", + "magento/module-catalog-analytics": "100.2.2", + "magento/module-catalog-import-export": "100.2.5", + "magento/module-catalog-inventory": "100.2.5", + "magento/module-catalog-rule": "101.0.5", + "magento/module-catalog-rule-configurable": "100.2.2", + "magento/module-catalog-search": "100.2.5", + "magento/module-catalog-url-rewrite": "100.2.5", + "magento/module-catalog-widget": "100.2.3", + "magento/module-checkout": "100.2.6", + "magento/module-checkout-agreements": "100.2.2", + "magento/module-cms": "102.0.6", + "magento/module-cms-url-rewrite": "100.2.2", + "magento/module-config": "101.0.6", + "magento/module-configurable-import-export": "100.2.3", + "magento/module-configurable-product": "100.2.6", + "magento/module-configurable-product-sales": "100.2.3", + "magento/module-contact": "100.2.3", + "magento/module-cookie": "100.2.2", + "magento/module-cron": "100.2.4", + "magento/module-currency-symbol": "100.2.2", + "magento/module-customer": "101.0.6", + "magento/module-customer-analytics": "100.2.2", + "magento/module-customer-import-export": "100.2.4", + "magento/module-deploy": "100.2.5", + "magento/module-developer": "100.2.4", + "magento/module-dhl": "100.2.3", + "magento/module-directory": "100.2.5", + "magento/module-downloadable": "100.2.5", + "magento/module-downloadable-import-export": "100.2.2", + "magento/module-eav": "101.0.5", + "magento/module-email": "100.2.4", + "magento/module-encryption-key": "100.2.2", + "magento/module-fedex": "100.2.3", + "magento/module-gift-message": "100.2.2", + "magento/module-google-adwords": "100.2.2", + "magento/module-google-analytics": "100.2.4", + "magento/module-google-optimizer": "100.2.3", + "magento/module-grouped-import-export": "100.2.2", + "magento/module-grouped-product": "100.2.4", + "magento/module-import-export": "100.2.6", + "magento/module-indexer": "100.2.4", + "magento/module-instant-purchase": "100.2.2", + "magento/module-integration": "100.2.4", + "magento/module-layered-navigation": "100.2.3", + "magento/module-media-storage": "100.2.2", + "magento/module-msrp": "100.2.2", + "magento/module-multishipping": "100.2.3", + "magento/module-new-relic-reporting": "100.2.4", + "magento/module-newsletter": "100.2.5", + "magento/module-offline-payments": "100.2.2", + "magento/module-offline-shipping": "100.2.4", + "magento/module-page-cache": "100.2.3", + "magento/module-payment": "100.2.4", + "magento/module-paypal": "100.2.4", + "magento/module-persistent": "100.2.2", + "magento/module-product-alert": "100.2.3", + "magento/module-product-video": "100.2.4", + "magento/module-quote": "101.0.5", + "magento/module-quote-analytics": "100.2.2", + "magento/module-release-notification": "100.2.3", + "magento/module-reports": "100.2.6", + "magento/module-require-js": "100.2.3", + "magento/module-review": "100.2.6", + "magento/module-review-analytics": "100.2.2", + "magento/module-robots": "100.2.3", + "magento/module-rss": "100.2.2", + "magento/module-rule": "100.2.3", + "magento/module-sales": "101.0.5", + "magento/module-sales-analytics": "100.2.2", + "magento/module-sales-inventory": "100.2.2", + "magento/module-sales-rule": "101.0.4", + "magento/module-sales-sequence": "100.2.2", + "magento/module-sample-data": "100.2.4", + "magento/module-search": "100.2.5", + "magento/module-security": "100.2.3", + "magento/module-send-friend": "100.2.2", + "magento/module-shipping": "100.2.6", + "magento/module-signifyd": "100.2.3", + "magento/module-sitemap": "100.2.5", + "magento/module-store": "100.2.5", + "magento/module-swagger-webapi": "100.2.0", + "magento/module-swagger": "100.2.4", + "magento/module-swatches": "100.2.4", + "magento/module-swatches-layered-navigation": "100.2.2", + "magento/module-tax": "100.2.6", + "magento/module-tax-import-export": "100.2.2", + "magento/module-theme": "100.2.6", + "magento/module-translation": "100.2.5", + "magento/module-ui": "101.0.6", + "magento/module-ups": "100.2.4", + "magento/module-url-rewrite": "101.0.5", + "magento/module-user": "101.0.4", + "magento/module-usps": "100.2.4", + "magento/module-variable": "100.2.5", + "magento/module-vault": "101.0.4", + "magento/module-version": "100.2.2", + "magento/module-webapi": "100.2.4", + "magento/module-webapi-security": "100.2.3", + "magento/module-weee": "100.2.3", + "magento/module-widget": "101.0.4", + "magento/module-wishlist": "101.0.4", + "magento/module-wishlist-analytics": "100.2.2", + "magento/theme-adminhtml-backend": "100.2.4", + "magento/theme-frontend-blank": "100.2.4", + "magento/theme-frontend-luma": "100.2.5", "magento/language-de_de": "100.2.0", "magento/language-en_us": "100.2.0", "magento/language-es_es": "100.2.0", @@ -211,7 +213,7 @@ "magento/language-nl_nl": "100.2.0", "magento/language-pt_br": "100.2.0", "magento/language-zh_hans_cn": "100.2.0", - "magento/framework": "101.0.5", + "magento/framework": "101.0.6", "trentrichardson/jquery-timepicker-addon": "1.4.3", "components/jquery": "1.11.0", "blueimp/jquery-file-upload": "5.6.14", diff --git a/composer.lock b/composer.lock index c63b89fa16ec2..c765d467dfd0c 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0b38b8779bcb06c6518a90750fde31f8", + "content-hash": "c9603f6f68dc6d6cc1ca6b6be242da74", "packages": [ { "name": "braintree/braintree_php", @@ -204,16 +204,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169" + "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/d2c0a83b7533d6912e8d516756ebd34f893e9169", - "reference": "d2c0a83b7533d6912e8d516756ebd34f893e9169", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/46afded9720f40b9dc63542af4e3e43a1177acb0", + "reference": "46afded9720f40b9dc63542af4e3e43a1177acb0", "shasum": "" }, "require": { @@ -256,7 +256,7 @@ "ssl", "tls" ], - "time": "2018-03-29T19:57:20+00:00" + "time": "2018-08-08T08:57:40+00:00" }, { "name": "composer/composer", @@ -859,16 +859,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.15", + "version": "v2.0.17", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09" + "reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/10bcb46e8f3d365170f6de9d05245aa066b81f09", - "reference": "10bcb46e8f3d365170f6de9d05245aa066b81f09", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/29af24f25bab834fcbb38ad2a69fa93b867e070d", + "reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d", "shasum": "" }, "require": { @@ -904,7 +904,7 @@ "pseudorandom", "random" ], - "time": "2018-06-08T15:26:40+00:00" + "time": "2018-07-04T16:31:37+00:00" }, { "name": "pelago/emogrifier", @@ -1436,16 +1436,16 @@ }, { "name": "symfony/console", - "version": "v2.8.41", + "version": "v2.8.46", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e8e59b74ad1274714dad2748349b55e3e6e630c7" + "reference": "aca0dcc0c75496e17e2aa0303bb9c8e6d79ed789" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e8e59b74ad1274714dad2748349b55e3e6e630c7", - "reference": "e8e59b74ad1274714dad2748349b55e3e6e630c7", + "url": "https://api.github.com/repos/symfony/console/zipball/aca0dcc0c75496e17e2aa0303bb9c8e6d79ed789", + "reference": "aca0dcc0c75496e17e2aa0303bb9c8e6d79ed789", "shasum": "" }, "require": { @@ -1493,7 +1493,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-15T21:17:45+00:00" + "time": "2018-09-30T03:33:07+00:00" }, { "name": "symfony/debug", @@ -1554,16 +1554,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.41", + "version": "v2.8.46", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9b69aad7d4c086dc94ebade2d5eb9145da5dac8c" + "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9b69aad7d4c086dc94ebade2d5eb9145da5dac8c", - "reference": "9b69aad7d4c086dc94ebade2d5eb9145da5dac8c", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/84ae343f39947aa084426ed1138bb96bf94d1f12", + "reference": "84ae343f39947aa084426ed1138bb96bf94d1f12", "shasum": "" }, "require": { @@ -1610,20 +1610,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06T07:35:03+00:00" + "time": "2018-07-26T09:03:18+00:00" }, { "name": "symfony/filesystem", - "version": "v3.4.11", + "version": "v3.4.17", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0" + "reference": "d69930fc337d767607267d57c20a7403d0a822a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0", - "reference": "8e03ca3fa52a0f56b87506f38cf7bd3f9442b3a0", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/d69930fc337d767607267d57c20a7403d0a822a4", + "reference": "d69930fc337d767607267d57c20a7403d0a822a4", "shasum": "" }, "require": { @@ -1660,20 +1660,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-05-16T08:49:21+00:00" + "time": "2018-10-02T12:28:39+00:00" }, { "name": "symfony/finder", - "version": "v3.4.11", + "version": "v3.4.17", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "472a92f3df8b247b49ae364275fb32943b9656c6" + "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/472a92f3df8b247b49ae364275fb32943b9656c6", - "reference": "472a92f3df8b247b49ae364275fb32943b9656c6", + "url": "https://api.github.com/repos/symfony/finder/zipball/54ba444dddc5bd5708a34bd095ea67c6eb54644d", + "reference": "54ba444dddc5bd5708a34bd095ea67c6eb54644d", "shasum": "" }, "require": { @@ -1709,29 +1709,32 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-05-16T08:49:21+00:00" + "time": "2018-10-03T08:46:40+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-ctype": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -1764,20 +1767,20 @@ "polyfill", "portable" ], - "time": "2018-04-30T19:57:29+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "3296adf6a6454a050679cde90f95350ad604b171" + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", - "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", "shasum": "" }, "require": { @@ -1789,7 +1792,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -1823,20 +1826,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/process", - "version": "v2.8.41", + "version": "v2.8.46", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "713952f2ccbcc8342ecdbe1cb313d3e2da8aad28" + "reference": "f09e21b7c5aba06c47bbfad9cbcf13ac7f0db0a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/713952f2ccbcc8342ecdbe1cb313d3e2da8aad28", - "reference": "713952f2ccbcc8342ecdbe1cb313d3e2da8aad28", + "url": "https://api.github.com/repos/symfony/process/zipball/f09e21b7c5aba06c47bbfad9cbcf13ac7f0db0a6", + "reference": "f09e21b7c5aba06c47bbfad9cbcf13ac7f0db0a6", "shasum": "" }, "require": { @@ -1872,20 +1875,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-15T21:17:45+00:00" + "time": "2018-09-06T17:11:15+00:00" }, { "name": "tedivm/jshrink", - "version": "v1.3.0", + "version": "v1.3.1", "source": { "type": "git", "url": "https://github.com/tedious/JShrink.git", - "reference": "68ce379b213741e86f02bf6053b0d26b9f833448" + "reference": "21254058dc3ce6aba6bef458cff4bfa25cf8b198" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tedious/JShrink/zipball/68ce379b213741e86f02bf6053b0d26b9f833448", - "reference": "68ce379b213741e86f02bf6053b0d26b9f833448", + "url": "https://api.github.com/repos/tedious/JShrink/zipball/21254058dc3ce6aba6bef458cff4bfa25cf8b198", + "reference": "21254058dc3ce6aba6bef458cff4bfa25cf8b198", "shasum": "" }, "require": { @@ -1918,7 +1921,53 @@ "javascript", "minifier" ], - "time": "2017-12-08T00:59:56+00:00" + "time": "2018-09-16T00:02:51+00:00" + }, + { + "name": "true/punycode", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/true/php-punycode.git", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/true/php-punycode/zipball/a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "reference": "a4d0c11a36dd7f4e7cd7096076cab6d3378a071e", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.7", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "TrueBV\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Renan Gonçalves", + "email": "renan.saddam@gmail.com" + } + ], + "description": "A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA)", + "homepage": "https://github.com/true/php-punycode", + "keywords": [ + "idna", + "punycode" + ], + "time": "2016-11-16T10:37:54+00:00" }, { "name": "tubalmartin/cssmin", @@ -2350,16 +2399,16 @@ }, { "name": "zendframework/zend-diactoros", - "version": "1.7.2", + "version": "1.8.6", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "741e7a571836f038de731105f4742ca8a164e43a" + "reference": "20da13beba0dde8fb648be3cc19765732790f46e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/741e7a571836f038de731105f4742ca8a164e43a", - "reference": "741e7a571836f038de731105f4742ca8a164e43a", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", + "reference": "20da13beba0dde8fb648be3cc19765732790f46e", "shasum": "" }, "require": { @@ -2372,18 +2421,29 @@ "require-dev": { "ext-dom": "*", "ext-libxml": "*", - "phpunit/phpunit": "^5.7.16 || ^6.0.8", + "php-http/psr7-integration-tests": "dev-master", + "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", "zendframework/zend-coding-standard": "~1.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev", - "dev-develop": "1.8.x-dev", + "dev-master": "1.8.x-dev", + "dev-develop": "1.9.x-dev", "dev-release-2.0": "2.0.x-dev" } }, "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php" + ], "psr-4": { "Zend\\Diactoros\\": "src/" } @@ -2399,7 +2459,7 @@ "psr", "psr-7" ], - "time": "2018-05-29T16:53:08+00:00" + "time": "2018-09-05T19:29:37+00:00" }, { "name": "zendframework/zend-escaper", @@ -2636,16 +2696,16 @@ }, { "name": "zendframework/zend-http", - "version": "2.8.0", + "version": "2.8.2", "source": { "type": "git", "url": "https://github.com/zendframework/zend-http.git", - "reference": "f48b276ffa11b48dd1ae3c6bc306d6ed7958ef51" + "reference": "2c8aed3d25522618573194e7cc51351f8cd4a45b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-http/zipball/f48b276ffa11b48dd1ae3c6bc306d6ed7958ef51", - "reference": "f48b276ffa11b48dd1ae3c6bc306d6ed7958ef51", + "url": "https://api.github.com/repos/zendframework/zend-http/zipball/2c8aed3d25522618573194e7cc51351f8cd4a45b", + "reference": "2c8aed3d25522618573194e7cc51351f8cd4a45b", "shasum": "" }, "require": { @@ -2687,7 +2747,7 @@ "zend", "zf" ], - "time": "2018-04-26T21:04:50+00:00" + "time": "2018-08-13T18:47:03+00:00" }, { "name": "zendframework/zend-hydrator", @@ -3039,6 +3099,68 @@ ], "time": "2018-04-09T21:59:51+00:00" }, + { + "name": "zendframework/zend-mail", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-mail.git", + "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-mail/zipball/d7beb63d5f7144a21ac100072c453e63860cdab8", + "reference": "d7beb63d5f7144a21ac100072c453e63860cdab8", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^5.6 || ^7.0", + "true/punycode": "^2.1", + "zendframework/zend-loader": "^2.5", + "zendframework/zend-mime": "^2.5", + "zendframework/zend-stdlib": "^2.7 || ^3.0", + "zendframework/zend-validator": "^2.10.2" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.25 || ^6.4.4 || ^7.1.4", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-config": "^2.6", + "zendframework/zend-crypt": "^2.6 || ^3.0", + "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1" + }, + "suggest": { + "zendframework/zend-crypt": "Crammd5 support in SMTP Auth", + "zendframework/zend-servicemanager": "^2.7.10 || ^3.3.1 when using SMTP to deliver messages" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.10.x-dev", + "dev-develop": "2.11.x-dev" + }, + "zf": { + "component": "Zend\\Mail", + "config-provider": "Zend\\Mail\\ConfigProvider" + } + }, + "autoload": { + "psr-4": { + "Zend\\Mail\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Provides generalized functionality to compose and send both text and MIME-compliant multipart e-mail messages", + "keywords": [ + "ZendFramework", + "mail", + "zf" + ], + "time": "2018-06-07T13:37:07+00:00" + }, { "name": "zendframework/zend-math", "version": "2.7.0", @@ -3089,6 +3211,57 @@ ], "time": "2016-04-07T16:29:53+00:00" }, + { + "name": "zendframework/zend-mime", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/zendframework/zend-mime.git", + "reference": "52ae5fa9f12845cae749271034a2d594f0e4c6f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/zendframework/zend-mime/zipball/52ae5fa9f12845cae749271034a2d594f0e4c6f2", + "reference": "52ae5fa9f12845cae749271034a2d594f0e4c6f2", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "zendframework/zend-stdlib": "^2.7 || ^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.7.21 || ^6.3", + "zendframework/zend-coding-standard": "~1.0.0", + "zendframework/zend-mail": "^2.6" + }, + "suggest": { + "zendframework/zend-mail": "Zend\\Mail component" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev", + "dev-develop": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Zend\\Mime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Create and parse MIME messages and parts", + "homepage": "https://github.com/zendframework/zend-mime", + "keywords": [ + "ZendFramework", + "mime", + "zf" + ], + "time": "2018-05-14T19:02:50+00:00" + }, { "name": "zendframework/zend-modulemanager", "version": "2.8.2", @@ -3884,150 +4057,142 @@ ], "packages-dev": [ { - "name": "composer/xdebug-handler", - "version": "1.1.0", + "name": "allure-framework/allure-codeception", + "version": "1.2.7", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08" + "url": "https://github.com/allure-framework/allure-codeception.git", + "reference": "48598f4b4603b50b663bfe977260113a40912131" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/c919dc6c62e221fc6406f861ea13433c0aa24f08", - "reference": "c919dc6c62e221fc6406f861ea13433c0aa24f08", + "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/48598f4b4603b50b663bfe977260113a40912131", + "reference": "48598f4b4603b50b663bfe977260113a40912131", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0", - "psr/log": "^1.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "allure-framework/allure-php-api": "~1.1.0", + "codeception/codeception": "~2.1", + "php": ">=5.4.0", + "symfony/filesystem": ">=2.6", + "symfony/finder": ">=2.6" }, "type": "library", "autoload": { - "psr-4": { - "Composer\\XdebugHandler\\": "src" + "psr-0": { + "Yandex": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" + "name": "Ivan Krutov", + "email": "vania-pooh@yandex-team.ru", + "role": "Developer" } ], - "description": "Restarts a process without xdebug.", + "description": "A Codeception adapter for Allure report.", + "homepage": "http://allure.qatools.ru/", "keywords": [ - "Xdebug", - "performance" - ], - "time": "2018-04-11T15:42:36+00:00" + "allure", + "attachments", + "cases", + "codeception", + "report", + "steps", + "testing" + ], + "time": "2018-03-07T11:18:27+00:00" }, { - "name": "doctrine/annotations", - "version": "v1.4.0", + "name": "allure-framework/allure-php-api", + "version": "1.1.4", "source": { "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + "url": "https://github.com/allure-framework/allure-php-adapter-api.git", + "reference": "a462a0da121681577033e13c123b6cc4e89cdc64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "url": "https://api.github.com/repos/allure-framework/allure-php-adapter-api/zipball/a462a0da121681577033e13c123b6cc4e89cdc64", + "reference": "a462a0da121681577033e13c123b6cc4e89cdc64", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" + "jms/serializer": ">=0.16.0", + "moontoast/math": ">=1.1.0", + "php": ">=5.4.0", + "phpunit/phpunit": ">=4.0.0", + "ramsey/uuid": ">=3.0.0", + "symfony/http-foundation": ">=2.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + "psr-0": { + "Yandex": [ + "src/", + "test/" + ] } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Ivan Krutov", + "email": "vania-pooh@yandex-team.ru", + "role": "Developer" } ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", + "description": "PHP API for Allure adapter", + "homepage": "http://allure.qatools.ru/", "keywords": [ - "annotations", - "docblock", - "parser" + "allure", + "api", + "php", + "report" ], - "time": "2017-02-24T16:22:25+00:00" + "time": "2016-12-07T12:15:46+00:00" }, { - "name": "doctrine/instantiator", - "version": "1.0.5", + "name": "behat/gherkin", + "version": "v4.4.5", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "url": "https://github.com/Behat/Gherkin.git", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/Behat/Gherkin/zipball/5c14cff4f955b17d20d088dec1bde61c0539ec74", + "reference": "5c14cff4f955b17d20d088dec1bde61c0539ec74", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": ">=5.3.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "~4.5|~5", + "symfony/phpunit-bridge": "~2.7|~3", + "symfony/yaml": "~2.3|~3" + }, + "suggest": { + "symfony/yaml": "If you want to parse features, represented in YAML files" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "4.4-dev" } }, "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "psr-0": { + "Behat\\Gherkin": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4036,45 +4201,93 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "description": "Gherkin DSL parser for PHP 5.3", + "homepage": "http://behat.org/", "keywords": [ - "constructor", - "instantiate" + "BDD", + "Behat", + "Cucumber", + "DSL", + "gherkin", + "parser" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2016-10-30T11:50:56+00:00" }, { - "name": "doctrine/lexer", - "version": "v1.0.1", + "name": "codeception/codeception", + "version": "2.3.9", "source": { "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "url": "https://github.com/Codeception/Codeception.git", + "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/Codeception/Codeception/zipball/104f46fa0bde339f1bcc3a375aac21eb36e65a1e", + "reference": "104f46fa0bde339f1bcc3a375aac21eb36e65a1e", "shasum": "" }, "require": { - "php": ">=5.3.2" + "behat/gherkin": "~4.4.0", + "codeception/stub": "^1.0", + "ext-json": "*", + "ext-mbstring": "*", + "facebook/webdriver": ">=1.1.3 <2.0", + "guzzlehttp/guzzle": ">=4.1.4 <7.0", + "guzzlehttp/psr7": "~1.0", + "php": ">=5.4.0 <8.0", + "phpunit/php-code-coverage": ">=2.2.4 <6.0", + "phpunit/phpunit": ">=4.8.28 <5.0.0 || >=5.6.3 <7.0", + "sebastian/comparator": ">1.1 <3.0", + "sebastian/diff": ">=1.4 <3.0", + "symfony/browser-kit": ">=2.7 <5.0", + "symfony/console": ">=2.7 <5.0", + "symfony/css-selector": ">=2.7 <5.0", + "symfony/dom-crawler": ">=2.7 <5.0", + "symfony/event-dispatcher": ">=2.7 <5.0", + "symfony/finder": ">=2.7 <5.0", + "symfony/yaml": ">=2.7 <5.0" + }, + "require-dev": { + "codeception/specify": "~0.3", + "facebook/graph-sdk": "~5.3", + "flow/jsonpath": "~0.2", + "monolog/monolog": "~1.8", + "pda/pheanstalk": "~3.0", + "php-amqplib/php-amqplib": "~2.4", + "predis/predis": "^1.0", + "squizlabs/php_codesniffer": "~2.0", + "symfony/process": ">=2.7 <5.0", + "vlucas/phpdotenv": "^2.4.0" + }, + "suggest": { + "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module", + "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests", + "codeception/specify": "BDD-style code blocks", + "codeception/verify": "BDD-style assertions", + "flow/jsonpath": "For using JSONPath in REST module", + "league/factory-muffin": "For DataFactory module", + "league/factory-muffin-faker": "For Faker support in DataFactory module", + "phpseclib/phpseclib": "for SFTP option in FTP Module", + "stecman/symfony-console-completion": "For BASH autocompletion", + "symfony/phpunit-bridge": "For phpunit-bridge support" }, + "bin": [ + "codecept" + ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "branch-alias": [] }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "psr-4": { + "Codeception\\": "src\\Codeception", + "Codeception\\Extension\\": "ext" } }, "notification-url": "https://packagist.org/downloads/", @@ -4083,151 +4296,191 @@ ], "authors": [ { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Michael Bodnarchuk", + "email": "davert@mail.ua", + "homepage": "http://codegyre.com" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", + "description": "BDD-style testing framework", + "homepage": "http://codeception.com/", "keywords": [ - "lexer", - "parser" + "BDD", + "TDD", + "acceptance testing", + "functional testing", + "unit testing" ], - "time": "2014-09-09T13:34:57+00:00" + "time": "2018-02-26T23:29:41+00:00" }, { - "name": "elasticsearch/elasticsearch", - "version": "v5.3.2", + "name": "codeception/stub", + "version": "1.0.4", "source": { "type": "git", - "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "4b29a4121e790bbfe690d5ee77da348b62d48eb8" + "url": "https://github.com/Codeception/Stub.git", + "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/4b29a4121e790bbfe690d5ee77da348b62d48eb8", - "reference": "4b29a4121e790bbfe690d5ee77da348b62d48eb8", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/681b62348837a5ef07d10d8a226f5bc358cc8805", + "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805", "shasum": "" }, "require": { - "guzzlehttp/ringphp": "~1.0", - "php": "^5.6|^7.0", - "psr/log": "~1.0" + "phpunit/phpunit-mock-objects": ">2.3 <7.0" }, "require-dev": { - "cpliakas/git-wrapper": "~1.0", - "doctrine/inflector": "^1.1", - "mockery/mockery": "0.9.4", - "phpunit/phpunit": "^4.7|^5.4", - "sami/sami": "~3.2", - "symfony/finder": "^2.8", - "symfony/yaml": "^2.8" + "phpunit/phpunit": ">=4.8 <8.0" }, - "suggest": { - "ext-curl": "*", - "monolog/monolog": "Allows for client-level logging and tracing" + "type": "library", + "autoload": { + "psr-4": { + "Codeception\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", + "time": "2018-05-17T09:31:08+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "1.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "reference": "b8e9745fb9b06ea6664d8872c4505fb16df4611c", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0", + "psr/log": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" }, "type": "library", "autoload": { "psr-4": { - "Elasticsearch\\": "src/Elasticsearch/" + "Composer\\XdebugHandler\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Zachary Tong" + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" } ], - "description": "PHP Client for Elasticsearch", + "description": "Restarts a process without xdebug.", "keywords": [ - "client", - "elasticsearch", - "search" + "Xdebug", + "performance" ], - "time": "2017-11-08T17:04:47+00:00" + "time": "2018-08-31T19:07:57+00:00" }, { - "name": "friendsofphp/php-cs-fixer", - "version": "v2.2.20", + "name": "consolidation/annotated-command", + "version": "2.9.1", "source": { "type": "git", - "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "f1631f0747ad2a9dd3de8d7873b71f6573f8d0c2" + "url": "https://github.com/consolidation/annotated-command.git", + "reference": "4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/f1631f0747ad2a9dd3de8d7873b71f6573f8d0c2", - "reference": "f1631f0747ad2a9dd3de8d7873b71f6573f8d0c2", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac", + "reference": "4bdbb8fa149e1cc1511bd77b0bc4729fd66bccac", "shasum": "" }, "require": { - "composer/semver": "^1.4", - "composer/xdebug-handler": "^1.0", - "doctrine/annotations": "^1.2", - "ext-json": "*", - "ext-tokenizer": "*", - "php": "^5.3.6 || >=7.0 <7.3", - "sebastian/diff": "^1.4", - "symfony/console": "^2.4 || ^3.0 || ^4.0", - "symfony/event-dispatcher": "^2.1 || ^3.0 || ^4.0", - "symfony/filesystem": "^2.4 || ^3.0 || ^4.0", - "symfony/finder": "^2.2 || ^3.0 || ^4.0", - "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0", - "symfony/polyfill-php54": "^1.0", - "symfony/polyfill-php55": "^1.3", - "symfony/polyfill-php70": "^1.0", - "symfony/polyfill-php72": "^1.4", - "symfony/process": "^2.3 || ^3.0 || ^4.0", - "symfony/stopwatch": "^2.5 || ^3.0 || ^4.0" + "consolidation/output-formatters": "^3.1.12", + "php": ">=5.4.0", + "psr/log": "^1", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4" }, - "conflict": { - "hhvm": "<3.18" + "require-dev": { + "g1a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^6", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\AnnotatedCommand\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Initialize Symfony Console commands from annotated command class methods.", + "time": "2018-09-19T17:47:18+00:00" + }, + { + "name": "consolidation/config", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/consolidation/config.git", + "reference": "c9fc25e9088a708637e18a256321addc0670e578" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/config/zipball/c9fc25e9088a708637e18a256321addc0670e578", + "reference": "c9fc25e9088a708637e18a256321addc0670e578", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "grasmash/expander": "^1", + "php": ">=5.4.0" }, "require-dev": { - "johnkary/phpunit-speedtrap": "^1.0.1 || ^2.0 || ^3.0", - "justinrainbow/json-schema": "^5.0", - "keradus/cli-executor": "^1.1", - "mikey179/vfsstream": "^1.6", - "php-coveralls/php-coveralls": "^1.0.2", - "phpunit/phpunit": "^4.8.35 || ^5.4.3", - "symfony/phpunit-bridge": "^3.2.2 || ^4.0" + "g1a/composer-test-scenarios": "^1", + "phpunit/phpunit": "^5", + "satooshi/php-coveralls": "^1.0", + "squizlabs/php_codesniffer": "2.*", + "symfony/console": "^2.5|^3|^4", + "symfony/yaml": "^2.8.11|^3|^4" }, "suggest": { - "ext-mbstring": "For handling non-UTF8 characters in cache signature.", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", - "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", "autoload": { "psr-4": { - "PhpCsFixer\\": "src/" - }, - "classmap": [ - "tests/Test/AbstractFixerTestCase.php", - "tests/Test/AbstractIntegrationCaseFactory.php", - "tests/Test/AbstractIntegrationTestCase.php", - "tests/Test/IntegrationCase.php", - "tests/Test/IntegrationCaseFactory.php", - "tests/Test/IntegrationCaseFactoryInterface.php", - "tests/Test/InternalIntegrationCaseFactory.php", - "tests/TestCase.php" - ] + "Consolidation\\Config\\": "src" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -4235,52 +4488,102 @@ ], "authors": [ { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - }, + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Provide configuration services for a commandline tool.", + "time": "2018-08-07T22:57:00+00:00" + }, + { + "name": "consolidation/log", + "version": "1.0.6", + "source": { + "type": "git", + "url": "https://github.com/consolidation/log.git", + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/log/zipball/dfd8189a771fe047bf3cd669111b2de5f1c79395", + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "psr/log": "~1.0", + "symfony/console": "^2.8|^3|^4" + }, + "require-dev": { + "g1a/composer-test-scenarios": "^1", + "phpunit/phpunit": "4.*", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "2.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Consolidation\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" } ], - "description": "A tool to automatically fix PHP code style", - "time": "2018-06-02T17:26:04+00:00" + "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", + "time": "2018-05-25T18:14:39+00:00" }, { - "name": "guzzlehttp/ringphp", - "version": "1.1.0", + "name": "consolidation/output-formatters", + "version": "3.2.1", "source": { "type": "git", - "url": "https://github.com/guzzle/RingPHP.git", - "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b" + "url": "https://github.com/consolidation/output-formatters.git", + "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", - "reference": "dbbb91d7f6c191e5e405e900e3102ac7f261bc0b", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", + "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", "shasum": "" }, "require": { - "guzzlehttp/streams": "~3.0", "php": ">=5.4.0", - "react/promise": "~2.0" + "symfony/console": "^2.8|^3|^4", + "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~4.0" + "g1a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^5.7.27", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.7", + "symfony/console": "3.2.3", + "symfony/var-dumper": "^2.8|^3|^4", + "victorjonsson/markdowndocs": "^1.3" }, "suggest": { - "ext-curl": "Guzzle will use specific adapters if cURL is present" + "symfony/var-dumper": "For using the var_dump formatter" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "GuzzleHttp\\Ring\\": "src/" + "Consolidation\\OutputFormatters\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4289,43 +4592,79 @@ ], "authors": [ { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" } ], - "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", - "time": "2015-05-20T03:37:09+00:00" + "description": "Format text by applying transformations provided by plug-in formatters.", + "time": "2018-05-25T18:02:34+00:00" }, { - "name": "guzzlehttp/streams", - "version": "3.0.0", + "name": "consolidation/robo", + "version": "1.3.1", "source": { "type": "git", - "url": "https://github.com/guzzle/streams.git", - "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" + "url": "https://github.com/consolidation/Robo.git", + "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", - "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", + "reference": "31f2d2562c4e1dcde70f2659eefd59aa9c7f5b2d", "shasum": "" }, "require": { - "php": ">=5.4.0" + "consolidation/annotated-command": "^2.8.2", + "consolidation/config": "^1.0.10", + "consolidation/log": "~1", + "consolidation/output-formatters": "^3.1.13", + "consolidation/self-update": "^1", + "g1a/composer-test-scenarios": "^2", + "grasmash/yaml-expander": "^1.3", + "league/container": "^2.2", + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4", + "symfony/event-dispatcher": "^2.5|^3|^4", + "symfony/filesystem": "^2.5|^3|^4", + "symfony/finder": "^2.5|^3|^4", + "symfony/process": "^2.5|^3|^4" + }, + "replace": { + "codegyre/robo": "< 1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "codeception/aspect-mock": "^1|^2.1.1", + "codeception/base": "^2.3.7", + "codeception/verify": "^0.3.2", + "goaop/framework": "~2.1.2", + "goaop/parser-reflection": "^1.1.0", + "natxet/cssmin": "3.0.4", + "nikic/php-parser": "^3.1.5", + "patchwork/jsqueeze": "~2", + "pear/archive_tar": "^1.4.2", + "phpunit/php-code-coverage": "~2|~4", + "satooshi/php-coveralls": "^2", + "squizlabs/php_codesniffer": "^2.8" + }, + "suggest": { + "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch", + "natxet/CssMin": "For minifying CSS files in taskMinify", + "patchwork/jsqueeze": "For minifying JS files in taskMinify", + "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively." }, + "bin": [ + "robo" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "1.x-dev", + "dev-state": "1.x-dev" } }, "autoload": { "psr-4": { - "GuzzleHttp\\Stream\\": "src/" + "Robo\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -4334,148 +4673,1448 @@ ], "authors": [ { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "name": "Davert", + "email": "davert.php@resend.cc" } ], - "description": "Provides a simple abstraction over streams of data", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "Guzzle", - "stream" + "description": "Modern task runner", + "time": "2018-08-17T18:44:18+00:00" + }, + { + "name": "consolidation/self-update", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/consolidation/self-update.git", + "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/consolidation/self-update/zipball/de33822f907e0beb0ffad24cf4b1b4fae5ada318", + "reference": "de33822f907e0beb0ffad24cf4b1b4fae5ada318", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "symfony/console": "^2.8|^3|^4", + "symfony/filesystem": "^2.5|^3|^4" + }, + "bin": [ + "scripts/release" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "SelfUpdate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + }, + { + "name": "Alexander Menk", + "email": "menk@mestrona.net" + } ], - "time": "2014-10-12T19:18:40+00:00" + "description": "Provides a self:update command for Symfony Console applications.", + "time": "2018-08-24T17:01:46+00:00" }, { - "name": "ircmaxell/password-compat", - "version": "v1.0.4", + "name": "dflydev/dot-access-data", + "version": "v1.1.0", "source": { "type": "git", - "url": "https://github.com/ircmaxell/password_compat.git", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", - "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a", + "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a", "shasum": "" }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Dflydev\\DotAccessData": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "time": "2017-01-20T21:14:22+00:00" + }, + { + "name": "doctrine/annotations", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", + "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2017-02-24T16:22:25+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03T10:49:41+00:00" + }, + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14T21:17:01+00:00" + }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, + { + "name": "epfremme/swagger-php", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/epfremmer/swagger-php.git", + "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/epfremmer/swagger-php/zipball/eee28a442b7e6220391ec953d3c9b936354f23bc", + "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.2", + "doctrine/collections": "^1.3", + "jms/serializer": "^1.1", + "php": ">=5.5", + "phpoption/phpoption": "^1.1", + "symfony/yaml": "^2.7|^3.1" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "~4.8|~5.0", + "satooshi/php-coveralls": "^1.0" + }, + "type": "package", + "autoload": { + "psr-4": { + "Epfremme\\Swagger\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Edward Pfremmer", + "email": "epfremme@nerdery.com" + } + ], + "description": "Library for parsing swagger documentation into PHP entities for use in testing and code generation", + "time": "2016-09-26T17:24:17+00:00" + }, + { + "name": "facebook/webdriver", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/facebook/php-webdriver.git", + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e", + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-zip": "*", + "php": "^5.6 || ~7.0", + "symfony/process": "^2.8 || ^3.1 || ^4.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "php-coveralls/php-coveralls": "^2.0", + "php-mock/php-mock-phpunit": "^1.1", + "phpunit/phpunit": "^5.7", + "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", + "squizlabs/php_codesniffer": "^2.6", + "symfony/var-dumper": "^3.3 || ^4.0" + }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-community": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Facebook\\WebDriver\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "A PHP client for Selenium WebDriver", + "homepage": "https://github.com/facebook/php-webdriver", + "keywords": [ + "facebook", + "php", + "selenium", + "webdriver" + ], + "time": "2018-05-16T17:37:13+00:00" + }, + { + "name": "flow/jsonpath", + "version": "0.4.0", + "source": { + "type": "git", + "url": "https://github.com/FlowCommunications/JSONPath.git", + "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/f0222818d5c938e4ab668ab2e2c079bd51a27112", + "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "peekmo/jsonpath": "dev-master", + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Flow\\JSONPath": "src/", + "Flow\\JSONPath\\Test": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Stephen Frank", + "email": "stephen@flowsa.com" + } + ], + "description": "JSONPath implementation for parsing, searching and flattening arrays", + "time": "2018-03-04T16:39:47+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v2.2.20", + "source": { + "type": "git", + "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", + "reference": "f1631f0747ad2a9dd3de8d7873b71f6573f8d0c2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/f1631f0747ad2a9dd3de8d7873b71f6573f8d0c2", + "reference": "f1631f0747ad2a9dd3de8d7873b71f6573f8d0c2", + "shasum": "" + }, + "require": { + "composer/semver": "^1.4", + "composer/xdebug-handler": "^1.0", + "doctrine/annotations": "^1.2", + "ext-json": "*", + "ext-tokenizer": "*", + "php": "^5.3.6 || >=7.0 <7.3", + "sebastian/diff": "^1.4", + "symfony/console": "^2.4 || ^3.0 || ^4.0", + "symfony/event-dispatcher": "^2.1 || ^3.0 || ^4.0", + "symfony/filesystem": "^2.4 || ^3.0 || ^4.0", + "symfony/finder": "^2.2 || ^3.0 || ^4.0", + "symfony/options-resolver": "^2.6 || ^3.0 || ^4.0", + "symfony/polyfill-php54": "^1.0", + "symfony/polyfill-php55": "^1.3", + "symfony/polyfill-php70": "^1.0", + "symfony/polyfill-php72": "^1.4", + "symfony/process": "^2.3 || ^3.0 || ^4.0", + "symfony/stopwatch": "^2.5 || ^3.0 || ^4.0" + }, + "conflict": { + "hhvm": "<3.18" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.0.1 || ^2.0 || ^3.0", + "justinrainbow/json-schema": "^5.0", + "keradus/cli-executor": "^1.1", + "mikey179/vfsstream": "^1.6", + "php-coveralls/php-coveralls": "^1.0.2", + "phpunit/phpunit": "^4.8.35 || ^5.4.3", + "symfony/phpunit-bridge": "^3.2.2 || ^4.0" + }, + "suggest": { + "ext-mbstring": "For handling non-UTF8 characters in cache signature.", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "For IsIdenticalString constraint.", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "For XmlMatchesXsd constraint.", + "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "classmap": [ + "tests/Test/AbstractFixerTestCase.php", + "tests/Test/AbstractIntegrationCaseFactory.php", + "tests/Test/AbstractIntegrationTestCase.php", + "tests/Test/IntegrationCase.php", + "tests/Test/IntegrationCaseFactory.php", + "tests/Test/IntegrationCaseFactoryInterface.php", + "tests/Test/InternalIntegrationCaseFactory.php", + "tests/TestCase.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "time": "2018-06-02T17:26:04+00:00" + }, + { + "name": "fzaninotto/faker", + "version": "v1.8.0", + "source": { + "type": "git", + "url": "https://github.com/fzaninotto/Faker.git", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de", + "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0" + }, + "require-dev": { + "ext-intl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7", + "squizlabs/php_codesniffer": "^1.5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.8-dev" + } + }, + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "time": "2018-07-12T10:23:15+00:00" + }, + { + "name": "g1a/composer-test-scenarios", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/g1a/composer-test-scenarios.git", + "reference": "a166fd15191aceab89f30c097e694b7cf3db4880" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/g1a/composer-test-scenarios/zipball/a166fd15191aceab89f30c097e694b7cf3db4880", + "reference": "a166fd15191aceab89f30c097e694b7cf3db4880", + "shasum": "" + }, + "bin": [ + "scripts/create-scenario", + "scripts/dependency-licenses", + "scripts/install-scenario" + ], + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Greg Anderson", + "email": "greg.1.anderson@greenknowe.org" + } + ], + "description": "Useful scripts for testing multiple sets of Composer dependencies.", + "time": "2018-08-08T23:37:23+00:00" + }, + { + "name": "grasmash/expander", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/expander.git", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\Expander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in PHP arrays file.", + "time": "2017-12-21T22:14:55+00:00" + }, + { + "name": "grasmash/yaml-expander", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/grasmash/yaml-expander.git", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/grasmash/yaml-expander/zipball/3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^1.1.0", + "php": ">=5.4", + "symfony/yaml": "^2.8.11|^3|^4" + }, + "require-dev": { + "greg-1-anderson/composer-test-scenarios": "^1", + "phpunit/phpunit": "^4.8|^5.5.4", + "satooshi/php-coveralls": "^1.0.2|dev-master", + "squizlabs/php_codesniffer": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Grasmash\\YamlExpander\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matthew Grasmick" + } + ], + "description": "Expands internal property references in a yaml file.", + "time": "2017-12-16T16:06:03+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2018-04-22T15:46:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "ircmaxell/password-compat", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/5c5cde8822a69545767f7c7f3058cb15ff84614c", + "reference": "5c5cde8822a69545767f7c7f3058cb15ff84614c", + "shasum": "" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ], + "time": "2014-11-20T16:49:30+00:00" + }, + { + "name": "jms/metadata", + "version": "1.6.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/metadata.git", + "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", + "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "doctrine/cache": "~1.0", + "symfony/cache": "~3.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Metadata\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Class/method/property metadata management in PHP", + "keywords": [ + "annotations", + "metadata", + "xml", + "yaml" + ], + "time": "2016-12-05T10:18:33+00:00" + }, + { + "name": "jms/parser-lib", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/parser-lib.git", + "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", + "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", + "shasum": "" + }, + "require": { + "phpoption/phpoption": ">=0.9,<2.0-dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "JMS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "description": "A library for easily creating recursive-descent parsers.", + "time": "2012-11-18T18:08:43+00:00" + }, + { + "name": "jms/serializer", + "version": "1.13.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/serializer.git", + "reference": "00863e1d55b411cc33ad3e1de09a4c8d3aae793c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/00863e1d55b411cc33ad3e1de09a4c8d3aae793c", + "reference": "00863e1d55b411cc33ad3e1de09a4c8d3aae793c", + "shasum": "" + }, + "require": { + "doctrine/annotations": "^1.0", + "doctrine/instantiator": "^1.0.3", + "jms/metadata": "^1.3", + "jms/parser-lib": "1.*", + "php": "^5.5|^7.0", + "phpcollection/phpcollection": "~0.1", + "phpoption/phpoption": "^1.1" + }, + "conflict": { + "twig/twig": "<1.12" + }, + "require-dev": { + "doctrine/orm": "~2.1", + "doctrine/phpcr-odm": "^1.3|^2.0", + "ext-pdo_sqlite": "*", + "jackalope/jackalope-doctrine-dbal": "^1.1.5", + "phpunit/phpunit": "^4.8|^5.0", + "propel/propel1": "~1.7", + "psr/container": "^1.0", + "symfony/dependency-injection": "^2.7|^3.3|^4.0", + "symfony/expression-language": "^2.6|^3.0", + "symfony/filesystem": "^2.1", + "symfony/form": "~2.1|^3.0", + "symfony/translation": "^2.1|^3.0", + "symfony/validator": "^2.2|^3.0", + "symfony/yaml": "^2.1|^3.0", + "twig/twig": "~1.12|~2.0" + }, + "suggest": { + "doctrine/cache": "Required if you like to use cache functionality.", + "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", + "symfony/yaml": "Required if you'd like to serialize data to YAML format." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-1.x": "1.13-dev" + } + }, + "autoload": { + "psr-0": { + "JMS\\Serializer": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + }, + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", + "homepage": "http://jmsyst.com/libs/serializer", + "keywords": [ + "deserialization", + "jaxb", + "json", + "serialization", + "xml" + ], + "time": "2018-07-25T13:58:54+00:00" + }, + { + "name": "league/container", + "version": "2.4.1", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/container.git", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", + "shasum": "" + }, + "require": { + "container-interop/container-interop": "^1.2", + "php": "^5.4.0 || ^7.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Container\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Phil Bennett", + "email": "philipobenito@gmail.com", + "homepage": "http://www.philipobenito.com", + "role": "Developer" + } + ], + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", + "keywords": [ + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" + ], + "time": "2017-05-10T09:20:27+00:00" + }, + { + "name": "lusitanian/oauth", + "version": "v0.8.11", + "source": { + "type": "git", + "url": "https://github.com/Lusitanian/PHPoAuthLib.git", + "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/fc11a53db4b66da555a6a11fce294f574a8374f9", + "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "3.7.*", + "predis/predis": "0.8.*@dev", + "squizlabs/php_codesniffer": "2.*", + "symfony/http-foundation": "~2.1" + }, + "suggest": { + "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.", + "predis/predis": "Allows using the Redis storage backend.", + "symfony/http-foundation": "Allows using the Symfony Session storage backend." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.1-dev" + } + }, + "autoload": { + "psr-0": { + "OAuth": "src", + "OAuth\\Unit": "tests" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Desberg", + "email": "david@daviddesberg.com" + }, + { + "name": "Elliot Chance", + "email": "elliotchance@gmail.com" + }, + { + "name": "Pieter Hordijk", + "email": "info@pieterhordijk.com" + } + ], + "description": "PHP 5.3+ oAuth 1/2 Library", + "keywords": [ + "Authentication", + "authorization", + "oauth", + "security" + ], + "time": "2018-02-14T22:37:14+00:00" + }, + { + "name": "magento/magento2-functional-testing-framework", + "version": "2.3.6", + "source": { + "type": "git", + "url": "https://github.com/magento/magento2-functional-testing-framework.git", + "reference": "57021e12ded213a0031c4d4f6293e06ce6f144ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/57021e12ded213a0031c4d4f6293e06ce6f144ce", + "reference": "57021e12ded213a0031c4d4f6293e06ce6f144ce", + "shasum": "" + }, + "require": { + "allure-framework/allure-codeception": "~1.2.6", + "codeception/codeception": "~2.3.4", + "consolidation/robo": "^1.0.0", + "epfremme/swagger-php": "^2.0", + "flow/jsonpath": ">0.2", + "fzaninotto/faker": "^1.6", + "monolog/monolog": "^1.0", + "mustache/mustache": "~2.5", + "php": "7.0.2|7.0.4|~7.0.6|~7.1.0|~7.2.0", + "symfony/process": "^2.8 || ^3.1 || ^4.0", + "vlucas/phpdotenv": "^2.4" + }, "require-dev": { - "phpunit/phpunit": "4.*" + "brainmaestro/composer-git-hooks": "^2.3", + "codacy/coverage": "^1.4", + "codeception/aspect-mock": "^3.0", + "doctrine/cache": "<1.7.0", + "goaop/framework": "2.2.0", + "php-coveralls/php-coveralls": "^1.0", + "phpmd/phpmd": "^2.6.0", + "rregeer/phpunit-coverage-check": "^0.1.4", + "sebastian/phpcpd": "~3.0 || ~4.0", + "squizlabs/php_codesniffer": "~3.2", + "symfony/stopwatch": "~3.4.6" }, + "bin": [ + "bin/mftf" + ], "type": "library", + "extra": { + "hooks": { + "pre-push": "bin/all-checks" + } + }, "autoload": { "files": [ - "lib/password.php" - ] + "src/Magento/FunctionalTestingFramework/_bootstrap.php" + ], + "psr-4": { + "Magento\\FunctionalTestingFramework\\": "src/Magento/FunctionalTestingFramework", + "MFTF\\": "dev/tests/functional/MFTF" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Anthony Ferrara", - "email": "ircmaxell@php.net", - "homepage": "http://blog.ircmaxell.com" - } + "AGPL-3.0" ], - "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", - "homepage": "https://github.com/ircmaxell/password_compat", + "description": "Magento2 Functional Testing Framework", "keywords": [ - "hashing", - "password" + "automation", + "functional", + "magento", + "testing" ], - "time": "2014-11-20T16:49:30+00:00" + "time": "2018-09-05T15:17:20+00:00" }, { - "name": "league/climate", - "version": "2.6.1", + "name": "moontoast/math", + "version": "1.1.2", "source": { "type": "git", - "url": "https://github.com/thephpleague/climate.git", - "reference": "28851c909017424f61cc6a62089316313c645d1c" + "url": "https://github.com/ramsey/moontoast-math.git", + "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/climate/zipball/28851c909017424f61cc6a62089316313c645d1c", - "reference": "28851c909017424f61cc6a62089316313c645d1c", + "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", + "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", "shasum": "" }, "require": { - "php": ">=5.4.0" + "ext-bcmath": "*", + "php": ">=5.3.3" }, "require-dev": { - "mockery/mockery": "dev-master", - "phpunit/phpunit": "4.1.*" + "jakub-onderka/php-parallel-lint": "^0.9.0", + "phpunit/phpunit": "^4.7|>=5.0 <5.4", + "satooshi/php-coveralls": "^0.6.1", + "squizlabs/php_codesniffer": "^2.3" }, "type": "library", "autoload": { "psr-4": { - "League\\CLImate\\": "src/" + "Moontoast\\Math\\": "src/Moontoast/Math/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache-2.0" ], "authors": [ { - "name": "Joe Tannenbaum", - "email": "hey@joe.codes", - "homepage": "http://joe.codes/", - "role": "Developer" + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" } ], - "description": "PHP's best friend for the terminal. CLImate allows you to easily output colored text, special formats, and more.", + "description": "A mathematics library, providing functionality for large numbers", + "homepage": "https://github.com/ramsey/moontoast-math", "keywords": [ - "cli", - "colors", - "command", - "php", - "terminal" + "bcmath", + "math" ], - "time": "2015-01-18T14:31:58+00:00" + "time": "2017-02-16T16:54:46+00:00" }, { - "name": "lusitanian/oauth", - "version": "v0.8.11", + "name": "mustache/mustache", + "version": "v2.12.0", "source": { "type": "git", - "url": "https://github.com/Lusitanian/PHPoAuthLib.git", - "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9" + "url": "https://github.com/bobthecow/mustache.php.git", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Lusitanian/PHPoAuthLib/zipball/fc11a53db4b66da555a6a11fce294f574a8374f9", - "reference": "fc11a53db4b66da555a6a11fce294f574a8374f9", + "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=5.2.4" }, "require-dev": { - "phpunit/phpunit": "3.7.*", - "predis/predis": "0.8.*@dev", - "squizlabs/php_codesniffer": "2.*", - "symfony/http-foundation": "~2.1" - }, - "suggest": { - "ext-openssl": "Allows for usage of secure connections with the stream-based HTTP client.", - "predis/predis": "Allows using the Redis storage backend.", - "symfony/http-foundation": "Allows using the Symfony Session storage backend." + "friendsofphp/php-cs-fixer": "~1.11", + "phpunit/phpunit": "~3.7|~4.0|~5.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.1-dev" - } - }, "autoload": { "psr-0": { - "OAuth": "src", - "OAuth\\Unit": "tests" + "Mustache": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -4484,26 +6123,18 @@ ], "authors": [ { - "name": "David Desberg", - "email": "david@daviddesberg.com" - }, - { - "name": "Elliot Chance", - "email": "elliotchance@gmail.com" - }, - { - "name": "Pieter Hordijk", - "email": "info@pieterhordijk.com" + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" } ], - "description": "PHP 5.3+ oAuth 1/2 Library", + "description": "A Mustache implementation in PHP.", + "homepage": "https://github.com/bobthecow/mustache.php", "keywords": [ - "Authentication", - "authorization", - "oauth", - "security" + "mustache", + "templating" ], - "time": "2018-02-14T22:37:14+00:00" + "time": "2017-07-11T12:54:05+00:00" }, { "name": "myclabs/deep-copy", @@ -4693,58 +6324,52 @@ "time": "2017-03-05T17:38:23+00:00" }, { - "name": "php-amqplib/php-amqplib", - "version": "v2.5.2", + "name": "phpcollection/phpcollection", + "version": "0.5.0", "source": { "type": "git", - "url": "https://github.com/php-amqplib/php-amqplib.git", - "reference": "eb8f94d97c8e79900accf77343dbd7eca7f58506" + "url": "https://github.com/schmittjoh/php-collection.git", + "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-amqplib/php-amqplib/zipball/eb8f94d97c8e79900accf77343dbd7eca7f58506", - "reference": "eb8f94d97c8e79900accf77343dbd7eca7f58506", + "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", + "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", "shasum": "" }, "require": { - "ext-bcmath": "*", - "ext-mbstring": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "3.7.*" - }, - "suggest": { - "ext-sockets": "Use AMQPSocketConnection" + "phpoption/phpoption": "1.*" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "0.4-dev" } }, "autoload": { - "psr-4": { - "PhpAmqpLib\\": "PhpAmqpLib/" + "psr-0": { + "PhpCollection": "src/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-2.1" + "Apache2" ], "authors": [ { - "name": "Alvaro Videla" + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" } ], - "description": "This library is a pure PHP implementation of the AMQP protocol. It's been tested against RabbitMQ.", - "homepage": "https://github.com/videlalvaro/php-amqplib/", + "description": "General-Purpose Collection Library for PHP", "keywords": [ - "message", - "queue", - "rabbitmq" + "collection", + "list", + "map", + "sequence", + "set" ], - "time": "2015-08-11T12:30:09+00:00" + "time": "2015-05-17T12:39:23+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -4964,18 +6589,68 @@ ], "time": "2017-01-20T14:41:10+00:00" }, + { + "name": "phpoption/phpoption", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/schmittjoh/php-option.git", + "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "4.7.*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3-dev" + } + }, + "autoload": { + "psr-0": { + "PhpOption\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache2" + ], + "authors": [ + { + "name": "Johannes M. Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Option Type for PHP", + "keywords": [ + "language", + "option", + "php", + "type" + ], + "time": "2015-07-25T16:39:46+00:00" + }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -4987,12 +6662,12 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -5025,7 +6700,7 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "time": "2018-08-05T17:53:17+00:00" }, { "name": "phpunit/php-code-coverage", @@ -5419,52 +7094,6 @@ ], "time": "2017-08-03T14:08:16+00:00" }, - { - "name": "react/promise", - "version": "v2.5.1", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "62785ae604c8d69725d693eb370e1d67e94c4053" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/62785ae604c8d69725d693eb370e1d67e94c4053", - "reference": "62785ae604c8d69725d693eb370e1d67e94c4053", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Promise\\": "src/" - }, - "files": [ - "src/functions_include.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "time": "2017-03-25T12:08:31+00:00" - }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -6105,53 +7734,168 @@ ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-10-03T07:35:21+00:00" + }, + { + "name": "squizlabs/php_codesniffer", + "version": "3.2.2", + "source": { + "type": "git", + "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", + "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "shasum": "" + }, + "require": { + "ext-simplexml": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + }, + "bin": [ + "bin/phpcs", + "bin/phpcbf" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Greg Sherwood", + "role": "lead" + } + ], + "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", + "homepage": "http://www.squizlabs.com/php-codesniffer", + "keywords": [ + "phpcs", + "standards" + ], + "time": "2017-12-19T21:44:46+00:00" + }, + { + "name": "symfony/browser-kit", + "version": "v3.4.17", + "source": { + "type": "git", + "url": "https://github.com/symfony/browser-kit.git", + "reference": "f6668d1a6182d5a8dec65a1c863a4c1d963816c0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f6668d1a6182d5a8dec65a1c863a4c1d963816c0", + "reference": "f6668d1a6182d5a8dec65a1c863a4c1d963816c0", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8", + "symfony/dom-crawler": "~2.8|~3.0|~4.0" + }, + "require-dev": { + "symfony/css-selector": "~2.8|~3.0|~4.0", + "symfony/process": "~2.8|~3.0|~4.0" + }, + "suggest": { + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\BrowserKit\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-10-03T07:35:21+00:00" + "description": "Symfony BrowserKit Component", + "homepage": "https://symfony.com", + "time": "2018-07-26T09:06:28+00:00" }, { - "name": "sjparkinson/static-review", - "version": "4.1.1", + "name": "symfony/config", + "version": "v3.4.17", "source": { "type": "git", - "url": "https://github.com/sjparkinson/static-review.git", - "reference": "493c3410cf146a12fca84209bad126c494e125f0" + "url": "https://github.com/symfony/config.git", + "reference": "e5389132dc6320682de3643091121c048ff796b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sjparkinson/static-review/zipball/493c3410cf146a12fca84209bad126c494e125f0", - "reference": "493c3410cf146a12fca84209bad126c494e125f0", + "url": "https://api.github.com/repos/symfony/config/zipball/e5389132dc6320682de3643091121c048ff796b3", + "reference": "e5389132dc6320682de3643091121c048ff796b3", "shasum": "" }, "require": { - "league/climate": "~2.0", - "php": ">=5.4.0", - "symfony/console": "~2.0", - "symfony/process": "~2.0" + "php": "^5.5.9|>=7.0.8", + "symfony/filesystem": "~2.8|~3.0|~4.0", + "symfony/polyfill-ctype": "~1.8" + }, + "conflict": { + "symfony/dependency-injection": "<3.3", + "symfony/finder": "<3.3" }, "require-dev": { - "mockery/mockery": "~0.9", - "phpunit/phpunit": "~4.0", - "sensiolabs/security-checker": "~2.0", - "squizlabs/php_codesniffer": "~1.0" + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/event-dispatcher": "~3.3|~4.0", + "symfony/finder": "~3.3|~4.0", + "symfony/yaml": "~3.0|~4.0" }, "suggest": { - "sensiolabs/security-checker": "Required for ComposerSecurityReview.", - "squizlabs/php_codesniffer": "Required for PhpCodeSnifferReview." + "symfony/yaml": "To use the yaml reference dumper" }, - "bin": [ - "bin/static-review.php" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, "autoload": { "psr-4": { - "StaticReview\\": "src/" - } + "Symfony\\Component\\Config\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -6159,155 +7903,165 @@ ], "authors": [ { - "name": "Samuel Parkinson", - "email": "sam.james.parkinson@gmail.com", - "homepage": "http://samp.im" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "An extendable framework for version control hooks.", - "abandoned": "phpro/grumphp", - "time": "2014-09-22T08:40:36+00:00" + "description": "Symfony Config Component", + "homepage": "https://symfony.com", + "time": "2018-09-08T13:15:14+00:00" }, { - "name": "solarium/solarium", - "version": "3.8.1", + "name": "symfony/css-selector", + "version": "v3.4.17", "source": { "type": "git", - "url": "https://github.com/solariumphp/solarium.git", - "reference": "c353babec89fdbe8c64054bfec8e77bcb5da6705" + "url": "https://github.com/symfony/css-selector.git", + "reference": "3503415d4aafabc31cd08c3a4ebac7f43fde8feb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/solariumphp/solarium/zipball/c353babec89fdbe8c64054bfec8e77bcb5da6705", - "reference": "c353babec89fdbe8c64054bfec8e77bcb5da6705", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/3503415d4aafabc31cd08c3a4ebac7f43fde8feb", + "reference": "3503415d4aafabc31cd08c3a4ebac7f43fde8feb", "shasum": "" }, "require": { - "php": ">=5.3.2", - "symfony/event-dispatcher": "~2.3|~3.0" - }, - "require-dev": { - "guzzlehttp/guzzle": "^3.8 || ^6.2", - "phpunit/phpunit": "~3.7", - "satooshi/php-coveralls": "~1.0", - "squizlabs/php_codesniffer": "~1.4", - "zendframework/zendframework1": "~1.12" - }, - "suggest": { - "minimalcode/search": "Query builder compatible with Solarium, allows simplified solr-query handling" + "php": "^5.5.9|>=7.0.8" }, "type": "library", "extra": { "branch-alias": { - "dev-develop": "3.3.x-dev" + "dev-master": "3.4-dev" } }, "autoload": { - "psr-0": { - "Solarium\\": "library/" - } + "psr-4": { + "Symfony\\Component\\CssSelector\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "See GitHub contributors", - "homepage": "https://github.com/basdenooijer/solarium/contributors" + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHP Solr client", - "homepage": "http://www.solarium-project.org", - "keywords": [ - "php", - "search", - "solr" - ], - "time": "2017-02-02T13:32:22+00:00" + "description": "Symfony CssSelector Component", + "homepage": "https://symfony.com", + "time": "2018-10-02T16:33:53+00:00" }, { - "name": "squizlabs/php_codesniffer", - "version": "3.2.2", + "name": "symfony/dependency-injection", + "version": "v3.3.18", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1" + "url": "https://github.com/symfony/dependency-injection.git", + "reference": "54243abc4e1a1a15e274e391bd6f7090b44711f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", - "reference": "d7c00c3000ac0ce79c96fcbfef86b49a71158cd1", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/54243abc4e1a1a15e274e391bd6f7090b44711f1", + "reference": "54243abc4e1a1a15e274e391bd6f7090b44711f1", "shasum": "" }, "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" + "php": "^5.5.9|>=7.0.8", + "psr/container": "^1.0" + }, + "conflict": { + "symfony/config": "<3.3.7", + "symfony/finder": "<3.3", + "symfony/yaml": "<3.3" + }, + "provide": { + "psr/container-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0" + "symfony/config": "~3.3", + "symfony/expression-language": "~2.8|~3.0", + "symfony/yaml": "~3.3" + }, + "suggest": { + "symfony/config": "", + "symfony/expression-language": "For using expressions in service container configuration", + "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", + "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", + "symfony/yaml": "" }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "3.3-dev" } }, + "autoload": { + "psr-4": { + "Symfony\\Component\\DependencyInjection\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Greg Sherwood", - "role": "lead" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", - "keywords": [ - "phpcs", - "standards" - ], - "time": "2017-12-19T21:44:46+00:00" + "description": "Symfony DependencyInjection Component", + "homepage": "https://symfony.com", + "time": "2018-01-29T09:02:23+00:00" }, { - "name": "symfony/config", - "version": "v3.4.11", + "name": "symfony/dom-crawler", + "version": "v3.4.17", "source": { "type": "git", - "url": "https://github.com/symfony/config.git", - "reference": "73e055cf2e6467715f187724a0347ea32079967c" + "url": "https://github.com/symfony/dom-crawler.git", + "reference": "c705bee03ade5b47c087807dd9ffaaec8dda2722" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/73e055cf2e6467715f187724a0347ea32079967c", - "reference": "73e055cf2e6467715f187724a0347ea32079967c", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/c705bee03ade5b47c087807dd9ffaaec8dda2722", + "reference": "c705bee03ade5b47c087807dd9ffaaec8dda2722", "shasum": "" }, "require": { "php": "^5.5.9|>=7.0.8", - "symfony/filesystem": "~2.8|~3.0|~4.0", - "symfony/polyfill-ctype": "~1.8" - }, - "conflict": { - "symfony/dependency-injection": "<3.3", - "symfony/finder": "<3.3" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { - "symfony/dependency-injection": "~3.3|~4.0", - "symfony/event-dispatcher": "~3.3|~4.0", - "symfony/finder": "~3.3|~4.0", - "symfony/yaml": "~3.0|~4.0" + "symfony/css-selector": "~2.8|~3.0|~4.0" }, "suggest": { - "symfony/yaml": "To use the yaml reference dumper" + "symfony/css-selector": "" }, "type": "library", "extra": { @@ -6317,7 +8071,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Config\\": "" + "Symfony\\Component\\DomCrawler\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6337,48 +8091,31 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Config Component", + "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2018-05-14T16:49:53+00:00" + "time": "2018-10-02T12:28:39+00:00" }, { - "name": "symfony/dependency-injection", - "version": "v3.4.11", + "name": "symfony/http-foundation", + "version": "v3.4.17", "source": { "type": "git", - "url": "https://github.com/symfony/dependency-injection.git", - "reference": "8a4672aca8db6d807905d695799ea7d83c8e5bba" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "3a4498236ade473c52b92d509303e5fd1b211ab1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/8a4672aca8db6d807905d695799ea7d83c8e5bba", - "reference": "8a4672aca8db6d807905d695799ea7d83c8e5bba", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/3a4498236ade473c52b92d509303e5fd1b211ab1", + "reference": "3a4498236ade473c52b92d509303e5fd1b211ab1", "shasum": "" }, "require": { "php": "^5.5.9|>=7.0.8", - "psr/container": "^1.0" - }, - "conflict": { - "symfony/config": "<3.3.7", - "symfony/finder": "<3.3", - "symfony/proxy-manager-bridge": "<3.4", - "symfony/yaml": "<3.4" - }, - "provide": { - "psr/container-implementation": "1.0" + "symfony/polyfill-mbstring": "~1.1", + "symfony/polyfill-php70": "~1.6" }, "require-dev": { - "symfony/config": "~3.3|~4.0", - "symfony/expression-language": "~2.8|~3.0|~4.0", - "symfony/yaml": "~3.4|~4.0" - }, - "suggest": { - "symfony/config": "", - "symfony/expression-language": "For using expressions in service container configuration", - "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required", - "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them", - "symfony/yaml": "" + "symfony/expression-language": "~2.8|~3.0|~4.0" }, "type": "library", "extra": { @@ -6388,7 +8125,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\DependencyInjection\\": "" + "Symfony\\Component\\HttpFoundation\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -6408,22 +8145,22 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony DependencyInjection Component", + "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2018-05-25T11:57:15+00:00" + "time": "2018-10-03T08:48:18+00:00" }, { "name": "symfony/options-resolver", - "version": "v3.4.11", + "version": "v3.4.17", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e" + "reference": "1cf7d8e704a9cc4164c92e430f2dfa3e6983661d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/f3109a6aedd20e35c3a33190e932c2b063b7b50e", - "reference": "f3109a6aedd20e35c3a33190e932c2b063b7b50e", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/1cf7d8e704a9cc4164c92e430f2dfa3e6983661d", + "reference": "1cf7d8e704a9cc4164c92e430f2dfa3e6983661d", "shasum": "" }, "require": { @@ -6464,20 +8201,20 @@ "configuration", "options" ], - "time": "2018-01-11T07:56:07+00:00" + "time": "2018-09-17T17:29:18+00:00" }, { "name": "symfony/polyfill-php54", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php54.git", - "reference": "6c3a2b84c6025e4ea3f6a19feac35408c64b22e1" + "reference": "412977e090c6a8472dc39d50d1beb7d59495a965" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/6c3a2b84c6025e4ea3f6a19feac35408c64b22e1", - "reference": "6c3a2b84c6025e4ea3f6a19feac35408c64b22e1", + "url": "https://api.github.com/repos/symfony/polyfill-php54/zipball/412977e090c6a8472dc39d50d1beb7d59495a965", + "reference": "412977e090c6a8472dc39d50d1beb7d59495a965", "shasum": "" }, "require": { @@ -6486,7 +8223,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -6522,20 +8259,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-php55", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php55.git", - "reference": "a39456128377a85f2c5707fcae458678560cba46" + "reference": "578b8528da843de0fc65ec395900fa3181f2ead7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/a39456128377a85f2c5707fcae458678560cba46", - "reference": "a39456128377a85f2c5707fcae458678560cba46", + "url": "https://api.github.com/repos/symfony/polyfill-php55/zipball/578b8528da843de0fc65ec395900fa3181f2ead7", + "reference": "578b8528da843de0fc65ec395900fa3181f2ead7", "shasum": "" }, "require": { @@ -6545,7 +8282,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -6578,30 +8315,30 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-php70", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6" + "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6", - "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/1e24b0c4a56d55aaf368763a06c6d1c7d3194934", + "reference": "1e24b0c4a56d55aaf368763a06c6d1c7d3194934", "shasum": "" }, "require": { - "paragonie/random_compat": "~1.0|~2.0", + "paragonie/random_compat": "~1.0|~2.0|~9.99", "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -6637,20 +8374,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.8.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46" + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/a4576e282d782ad82397f3e4ec1df8e0f0cafb46", - "reference": "a4576e282d782ad82397f3e4ec1df8e0f0cafb46", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/95c50420b0baed23852452a7f0c7b527303ed5ae", + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae", "shasum": "" }, "require": { @@ -6659,7 +8396,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -6692,20 +8429,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/stopwatch", - "version": "v3.4.11", + "version": "v3.4.17", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "eb17cfa072cab26537ac37e9c4ece6c0361369af" + "reference": "05e52a39de52ba690aebaed462b2bc8a9649f0a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/eb17cfa072cab26537ac37e9c4ece6c0361369af", - "reference": "eb17cfa072cab26537ac37e9c4ece6c0361369af", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/05e52a39de52ba690aebaed462b2bc8a9649f0a4", + "reference": "05e52a39de52ba690aebaed462b2bc8a9649f0a4", "shasum": "" }, "require": { @@ -6741,7 +8478,62 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-02-17T14:55:25+00:00" + "time": "2018-10-02T12:28:39+00:00" + }, + { + "name": "symfony/yaml", + "version": "v3.3.18", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "af615970e265543a26ee712c958404eb9b7ac93d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/af615970e265543a26ee712c958404eb9b7ac93d", + "reference": "af615970e265543a26ee712c958404eb9b7ac93d", + "shasum": "" + }, + "require": { + "php": "^5.5.9|>=7.0.8" + }, + "require-dev": { + "symfony/console": "~2.8|~3.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2018-01-20T15:04:53+00:00" }, { "name": "theseer/fdomdocument", @@ -6823,6 +8615,56 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "time": "2017-04-07T12:08:54+00:00" }, + { + "name": "vlucas/phpdotenv", + "version": "v2.5.1", + "source": { + "type": "git", + "url": "https://github.com/vlucas/phpdotenv.git", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "Dotenv\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.", + "keywords": [ + "dotenv", + "env", + "environment" + ], + "time": "2018-07-29T20:33:41+00:00" + }, { "name": "webmozart/assert", "version": "1.3.0", diff --git a/dev/tests/acceptance/LICENSE.txt b/dev/tests/acceptance/LICENSE.txt deleted file mode 100644 index 49525fd99da9c..0000000000000 --- a/dev/tests/acceptance/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Open Software License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/dev/tests/acceptance/LICENSE_AFL.txt b/dev/tests/acceptance/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/dev/tests/acceptance/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Academic Free License ("AFL") v. 3.0 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Academic Free License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/dev/tests/acceptance/README.md b/dev/tests/acceptance/README.md deleted file mode 100755 index 6350b9cabcdfa..0000000000000 --- a/dev/tests/acceptance/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# Magento Functional Testing Framework - ----- - -## System Requirements -[Magento Functional Testing Framework system requirements](http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/getting-started.html#prepare-environment) - -## Installation -To install the Magento Functional Testing Framework, see [Getting Started](http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/getting-started.html) - -## Contributing -Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions. - -To learn about how to make a contribution, click [here][1]. - -To open an issue, click [here][2]. - -To suggest documentation improvements, click [here][3]. - -[1]: <http://devdocs.magento.com/guides/v2.2/magento-functional-testing-framework/contribution-guidelines.html> -[2]: <https://github.com/magento/magento2-functional-testing-framework/issues> -[3]: <http://devdocs.magento.com> - -### Labels applied by the MFTF team - -Refer to the tables with descriptions of each label below. These labels are applied by the MFTF development team to community contributed issues and pull requests, to communicate status, impact, or which team is working on it. - -### Pull Request Status - -Label| Description ----|--- -**accept**| The pull request has been accepted and will be merged into mainline code. -**reject**| The pull request has been rejected and will not be merged into mainline code. Possible reasons can include but are not limited to: issue has already been fixed in another code contribution, or there is an issue with the code contribution. -**needsUpdate**| The Magento Team needs additional information from the reporter to properly prioritize and process the pull request. - -### Issue Resolution Status - -Label| Description ----|--- -**acknowledged**| The Magento Team has validated the issue and an internal ticket has been created. -**needsUpdate**| The Magento Team needs additional information from the reporter to properly prioritize and process the issue or pull request. -**cannot reproduce**| The Magento Team has not confirmed that this issue contains the minimum required information to reproduce. -**non-issue**| The Magento Team has not recognised any issue according to provided information. - -### Domains Impacted - -Label| Description ----|--- -**PROD**| Affects the Product team (mostly feature requests or business logic change). -**DOC**| Affects Documentation domain. -**TECH**| Affects Architect Group (mostly to make decisions around technology changes). - -### Type - -Label| Description ----|--- -**bugfix**| The issue or pull request relates to bug fixing. -**enhancement**| The issue or pull request that raises the MFTF to a higher degree (for example new features, optimization, refactoring, etc). - -## License - -Each Magento source file included in this distribution is licensed under APL 3.0 - -Please see LICENSE_APL3.txt for the full text of the APL 3.0 license or contact license@magentocommerce.com for a copy. diff --git a/dev/tests/acceptance/RoboFile.php b/dev/tests/acceptance/RoboFile.php index dd84b4131e502..f36150ad254b5 100644 --- a/dev/tests/acceptance/RoboFile.php +++ b/dev/tests/acceptance/RoboFile.php @@ -15,84 +15,6 @@ class RoboFile extends \Robo\Tasks { use Robo\Task\Base\loadShortcuts; - public function __construct() - { - require 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; - define('VENDOR_BIN_PATH', PROJECT_ROOT . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR); - - } - /** - * Duplicate the Example configuration files used to customize the Project for customization. - * - * @return void - */ - function cloneFiles() - { - $this->_exec('cp -vn .env.example .env'); - $this->_exec('cp -vf codeception.dist.yml codeception.yml'); - $this->_exec('cp -vf tests'. DIRECTORY_SEPARATOR .'functional.suite.dist.yml tests'. DIRECTORY_SEPARATOR .'functional.suite.yml'); - } - - /** - * Finds relative paths between codeception.yml file and MFTF path, and overwrites the default paths. - * - * @return void - */ - private function buildCodeceptionPaths() - { - $relativePathFunc = function ($from, $to) - { - $from = is_dir($from) ? rtrim($from, '\/') . '/' : $from; - $to = is_dir($to) ? rtrim($to, '\/') . '/' : $to; - $from = str_replace('\\', '/', $from); - $to = str_replace('\\', '/', $to); - - $from = explode('/', $from); - $to = explode('/', $to); - $relPath = $to; - - foreach($from as $depth => $dir) { - // find first non-matching dir - if($dir === $to[$depth]) { - // ignore this directory - array_shift($relPath); - } else { - // get number of remaining dirs to $from - $remaining = count($from) - $depth; - if($remaining > 1) { - // add traversals up to first matching dir - $padLength = (count($relPath) + $remaining - 1) * -1; - $relPath = array_pad($relPath, $padLength, '..'); - break; - } else { - $relPath[0] = './' . $relPath[0]; - } - } - } - return implode('/', $relPath); - }; - - //Find travel path from codeception.yml to FW_BP - $configYmlPath = dirname(dirname(TESTS_BP)) . DIRECTORY_SEPARATOR; - $relativePath = call_user_func($relativePathFunc, $configYmlPath, FW_BP); - $configYmlFile = $configYmlPath . "codeception.yml"; - $defaultConfigYmlFile = $configYmlPath . "codeception.dist.yml"; - - if (file_exists($configYmlFile)) { - $ymlContents = file_get_contents($configYmlFile); - } else { - $ymlContents = file_get_contents($defaultConfigYmlFile); - } - $ymlArray = Yaml::parse($ymlContents) ?? []; - if (!array_key_exists("paths", $ymlArray)) { - $ymlArray["paths"] = []; - } - $ymlArray["paths"]["support"] = $relativePath . 'src/Magento/FunctionalTestingFramework'; - $ymlArray["paths"]["envs"] = $relativePath . 'etc/_envs'; - $ymlText = Yaml::dump($ymlArray, 10); - file_put_contents($configYmlFile, $ymlText); - } - /** * Duplicate the Example configuration files for the Project. * Build the Codeception project. @@ -101,9 +23,7 @@ private function buildCodeceptionPaths() */ function buildProject() { - $this->cloneFiles(); - $this->buildCodeceptionPaths(); - $this->_exec(VENDOR_BIN_PATH .'codecept build'); + passthru($this->getBaseCmd("build:project")); } /** @@ -117,93 +37,26 @@ function generateTests(array $tests, $opts = [ 'config' => null, 'force' => false, 'nodes' => null, - 'lines' => 500, + 'lines' => null, 'tests' => null ]) { - require 'tests'. DIRECTORY_SEPARATOR . 'functional' . DIRECTORY_SEPARATOR . '_bootstrap.php'; - $testConfiguration = $this->createTestConfiguration($tests, $opts); - - // maintain backwards compatability for devops by not removing the nodes option yet - $lines = $opts['lines']; - - // create our manifest file here - $testManifest = \Magento\FunctionalTestingFramework\Util\Manifest\TestManifestFactory::makeManifest($opts['config'],$testConfiguration['suites']); - \Magento\FunctionalTestingFramework\Util\TestGenerator::getInstance(null, $testConfiguration['tests'])->createAllTestFiles($testManifest); - - if ($opts['config'] == 'parallel') { - $testManifest->createTestGroups($lines); - } - - \Magento\FunctionalTestingFramework\Suite\SuiteGenerator::getInstance()->generateAllSuites($testManifest); - $testManifest->generate(); - - $this->say("Generate Tests Command Run"); - } - - - /** - * Function which builds up a configuration including test and suites for consumption of Magento generation methods. - * - * @param array $tests - * @param array $opts - * @return array - */ - private function createTestConfiguration($tests, $opts) - { - // set our application configuration so we can references the user options in our framework - Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::create( - $opts['force'], - Magento\FunctionalTestingFramework\Config\MftfApplicationConfig::GENERATION_PHASE, - $opts['verbose'] - ); + $baseCmd = $this->getBaseCmd("generate:tests"); - $testConfiguration = []; - $testConfiguration['tests'] = $tests; - $testConfiguration['suites'] = []; - - $testConfiguration = $this->parseTestsConfigJson($opts['tests'], $testConfiguration); - - // if we have references to specific tests, we resolve the test objects and pass them to the config - if (!empty($testConfiguration['tests'])) - { - $testObjects = []; - - foreach ($testConfiguration['tests'] as $test) - { - $testObjects[$test] = Magento\FunctionalTestingFramework\Test\Handlers\TestObjectHandler::getInstance()->getObject($test); + $mftfArgNames = ['config', 'nodes', 'lines', 'tests']; + // append arguments to the end of the command + foreach ($opts as $argName => $argValue) { + if (in_array($argName, $mftfArgNames) && $argValue !== null) { + $baseCmd .= " --$argName $argValue"; } - - $testConfiguration['tests'] = $testObjects; } - return $testConfiguration; - } - - /** - * Function which takes a json string of potential custom configuration and parses/validates the resulting json - * passed in by the user. The result is a testConfiguration array. - * - * @param string $json - * @param array $testConfiguration - * @return array - */ - private function parseTestsConfigJson($json, $testConfiguration) { - if ($json == null) { - return $testConfiguration; + // use a separate conditional for the force flag (casting bool to string in php is hard) + if ($opts['force']) { + $baseCmd .= ' --force'; } - $jsonTestConfiguration = []; - $testConfigArray = json_decode($json, true); - - // stop execution if we have failed to properly parse any json - if (json_last_error() != JSON_ERROR_NONE) { - throw new \Magento\FunctionalTestingFramework\Exceptions\TestFrameworkException("JSON could not be parsed: " . json_last_error_msg()); - } - - $jsonTestConfiguration['tests'] = $testConfigArray['tests'] ?? null;; - $jsonTestConfiguration['suites'] = $testConfigArray['suites'] ?? null; - return $jsonTestConfiguration; + $this->taskExec($baseCmd)->args($tests)->run(); } /** @@ -218,54 +71,21 @@ function generateSuite(array $args) if (empty($args)) { throw new Exception("Please provide suite name(s) after generate:suite command"); } - - $sg = \Magento\FunctionalTestingFramework\Suite\SuiteGenerator::getInstance(); - - foreach ($args as $arg) { - $sg->generateSuite($arg); - } - } - - /** - * Run all Functional tests. - * - * @return void - */ - function functional() - { - $this->_exec(VENDOR_BIN_PATH . 'codecept run functional'); + $baseCmd = $this->getBaseCmd("generate:suite"); + $this->taskExec($baseCmd)->args($args)->run(); } /** * Run all Tests with the specified @group tag'. * - * @param string $args - * @return void - */ - function group($args = '') - { - $this->taskExec(VENDOR_BIN_PATH . 'codecept run functional --verbose --steps --group')->args($args)->run(); - } - - /** - * Run all Functional tests located under the Directory Path provided. - * - * @param string $args - * @return void - */ - function folder($args = '') - { - $this->taskExec(VENDOR_BIN_PATH . 'codecept run functional')->args($args)->run(); - } - - /** - * Run all Tests marked with the @group tag 'example'. - * + * @param array $args * @return void */ - function example() + function group(array $args) { - $this->_exec(VENDOR_BIN_PATH . 'codecept run --group example'); + $args = array_merge($args, ['-k']); + $baseCmd = $this->getBaseCmd("run:group"); + $this->taskExec($baseCmd)->args($args)->run(); } /** @@ -337,12 +157,15 @@ function allure2Report() } /** - * Run the Pre-Install system check script. + * Private function for returning the formatted command for the passthru to mftf bin execution. * - * @return void + * @param string $command + * @return string */ - function preInstall() + private function getBaseCmd($command) { - $this->_exec('php pre-install.php'); + $this->writeln("\033[01;31m Use of robo will be deprecated with next major release, please use <root>/vendor/bin/mftf $command \033[0m"); + chdir(__DIR__); + return realpath('../../../vendor/bin/mftf') . " $command"; } } diff --git a/dev/tests/acceptance/codeception.dist.yml b/dev/tests/acceptance/codeception.dist.yml deleted file mode 100644 index 0e4c6cadd9f81..0000000000000 --- a/dev/tests/acceptance/codeception.dist.yml +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright © Magento, Inc. All rights reserved. -# See COPYING.txt for license details. -actor: Tester -paths: - tests: tests - log: tests/_output - data: tests/_data - support: vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework - envs: vendor/magento/magento2-functional-testing-framework/etc/_envs -settings: - bootstrap: _bootstrap.php - colors: true - memory_limit: 1024M -extensions: - enabled: - - Codeception\Extension\RunFailed - - Yandex\Allure\Adapter\AllureAdapter - - Magento\FunctionalTestingFramework\Extension\TestContextExtension - config: - Yandex\Allure\Adapter\AllureAdapter: - deletePreviousResults: true - outputDirectory: allure-results - ignoredAnnotations: - - env - - zephyrId - - useCaseId -params: - - .env -modules: - config: - Db: - dsn: "%DB_DSN%" - user: "%DB_USERNAME%" - password: "%DB_PASSWORD%" - dump: tests/_data/dump.sql \ No newline at end of file diff --git a/dev/tests/acceptance/composer.json b/dev/tests/acceptance/composer.json index 5d50f15d91a49..a0c3ad37f47a3 100755 --- a/dev/tests/acceptance/composer.json +++ b/dev/tests/acceptance/composer.json @@ -9,20 +9,23 @@ "config": { "sort-packages": true }, - "repositories": [ - { - "type": "git", - "url": "git@github.com:magento/magento2-functional-testing-framework.git" - } - ], "require": { - "magento/magento2-functional-testing-framework": "~2.2.0", - "php": "~7.0.13|~7.1.0" + "php": "~7.0.13|~7.1.0", + "codeception/codeception": "~2.3.4", + "consolidation/robo": "^1.0.0", + "vlucas/phpdotenv": "^2.4", + "doctrine/instantiator": "<=1.0.5", + "myclabs/deep-copy": "<=1.7.0", + "symfony/filesystem": "<=3.4.12", + "symfony/finder": "<=3.4.12", + "symfony/process": "<=3.4.12", + "symfony/yaml": "<=3.4.12" }, "autoload": { "psr-4": { "Magento\\": "tests/functional/Magento" - } + }, + "files": ["tests/_bootstrap.php"] }, "prefer-stable": true } diff --git a/dev/tests/acceptance/composer.lock b/dev/tests/acceptance/composer.lock index da6815ddcc853..1592e385dd5c0 100644 --- a/dev/tests/acceptance/composer.lock +++ b/dev/tests/acceptance/composer.lock @@ -1,114 +1,11 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "51c4a12db0e6ad7246a727f6a4413d37", + "content-hash": "87a504ae6c961babfa9113e81f9345fc", "packages": [ - { - "name": "allure-framework/allure-codeception", - "version": "1.2.7", - "source": { - "type": "git", - "url": "https://github.com/allure-framework/allure-codeception.git", - "reference": "48598f4b4603b50b663bfe977260113a40912131" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-codeception/zipball/48598f4b4603b50b663bfe977260113a40912131", - "reference": "48598f4b4603b50b663bfe977260113a40912131", - "shasum": "" - }, - "require": { - "allure-framework/allure-php-api": "~1.1.0", - "codeception/codeception": "~2.1", - "php": ">=5.4.0", - "symfony/filesystem": ">=2.6", - "symfony/finder": ">=2.6" - }, - "type": "library", - "autoload": { - "psr-0": { - "Yandex": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Ivan Krutov", - "email": "vania-pooh@yandex-team.ru", - "role": "Developer" - } - ], - "description": "A Codeception adapter for Allure report.", - "homepage": "http://allure.qatools.ru/", - "keywords": [ - "allure", - "attachments", - "cases", - "codeception", - "report", - "steps", - "testing" - ], - "time": "2018-03-07T11:18:27+00:00" - }, - { - "name": "allure-framework/allure-php-api", - "version": "1.1.4", - "source": { - "type": "git", - "url": "https://github.com/allure-framework/allure-php-adapter-api.git", - "reference": "a462a0da121681577033e13c123b6cc4e89cdc64" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/allure-framework/allure-php-adapter-api/zipball/a462a0da121681577033e13c123b6cc4e89cdc64", - "reference": "a462a0da121681577033e13c123b6cc4e89cdc64", - "shasum": "" - }, - "require": { - "jms/serializer": ">=0.16.0", - "moontoast/math": ">=1.1.0", - "php": ">=5.4.0", - "phpunit/phpunit": ">=4.0.0", - "ramsey/uuid": ">=3.0.0", - "symfony/http-foundation": ">=2.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Yandex": [ - "src/", - "test/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Ivan Krutov", - "email": "vania-pooh@yandex-team.ru", - "role": "Developer" - } - ], - "description": "PHP API for Allure adapter", - "homepage": "http://allure.qatools.ru/", - "keywords": [ - "allure", - "api", - "php", - "report" - ], - "time": "2016-12-07T12:15:46+00:00" - }, { "name": "behat/gherkin", "version": "v4.4.5", @@ -264,16 +161,16 @@ }, { "name": "codeception/stub", - "version": "1.0.2", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/Codeception/Stub.git", - "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99" + "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Codeception/Stub/zipball/95fb7a36b81890dd2e5163e7ab31310df6f1bb99", - "reference": "95fb7a36b81890dd2e5163e7ab31310df6f1bb99", + "url": "https://api.github.com/repos/Codeception/Stub/zipball/681b62348837a5ef07d10d8a226f5bc358cc8805", + "reference": "681b62348837a5ef07d10d8a226f5bc358cc8805", "shasum": "" }, "require": { @@ -293,20 +190,20 @@ "MIT" ], "description": "Flexible Stub wrapper for PHPUnit's Mock Builder", - "time": "2018-02-18T13:56:56+00:00" + "time": "2018-05-17T09:31:08+00:00" }, { "name": "consolidation/annotated-command", - "version": "2.8.3", + "version": "2.8.4", "source": { "type": "git", "url": "https://github.com/consolidation/annotated-command.git", - "reference": "8f8f5da2ca06fbd3a85f7d551c49f844b7c59437" + "reference": "651541a0b68318a2a202bda558a676e5ad92223c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/8f8f5da2ca06fbd3a85f7d551c49f844b7c59437", - "reference": "8f8f5da2ca06fbd3a85f7d551c49f844b7c59437", + "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/651541a0b68318a2a202bda558a676e5ad92223c", + "reference": "651541a0b68318a2a202bda558a676e5ad92223c", "shasum": "" }, "require": { @@ -318,9 +215,9 @@ "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "greg-1-anderson/composer-test-scenarios": "^1", - "phpunit/phpunit": "^4.8", - "satooshi/php-coveralls": "^1.0.2 | dev-master", + "g1a/composer-test-scenarios": "^2", + "phpunit/phpunit": "^6", + "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.7" }, "type": "library", @@ -345,20 +242,20 @@ } ], "description": "Initialize Symfony Console commands from annotated command class methods.", - "time": "2018-02-23T16:32:04+00:00" + "time": "2018-05-25T18:04:25+00:00" }, { "name": "consolidation/config", - "version": "1.0.9", + "version": "1.0.11", "source": { "type": "git", "url": "https://github.com/consolidation/config.git", - "reference": "34ca8d7c1ee60a7b591b10617114cf1210a2e92c" + "reference": "ede41d946078e97e7a9513aadc3352f1c26817af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/config/zipball/34ca8d7c1ee60a7b591b10617114cf1210a2e92c", - "reference": "34ca8d7c1ee60a7b591b10617114cf1210a2e92c", + "url": "https://api.github.com/repos/consolidation/config/zipball/ede41d946078e97e7a9513aadc3352f1c26817af", + "reference": "ede41d946078e97e7a9513aadc3352f1c26817af", "shasum": "" }, "require": { @@ -367,7 +264,7 @@ "php": ">=5.4.0" }, "require-dev": { - "greg-1-anderson/composer-test-scenarios": "^1", + "g1a/composer-test-scenarios": "^1", "phpunit/phpunit": "^4", "satooshi/php-coveralls": "^1.0", "squizlabs/php_codesniffer": "2.*", @@ -399,20 +296,20 @@ } ], "description": "Provide configuration services for a commandline tool.", - "time": "2017-12-22T17:28:19+00:00" + "time": "2018-05-27T01:17:02+00:00" }, { "name": "consolidation/log", - "version": "1.0.5", + "version": "1.0.6", "source": { "type": "git", "url": "https://github.com/consolidation/log.git", - "reference": "dbc7c535f319a4a2d5a5077738f8eb7c10df8821" + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/log/zipball/dbc7c535f319a4a2d5a5077738f8eb7c10df8821", - "reference": "dbc7c535f319a4a2d5a5077738f8eb7c10df8821", + "url": "https://api.github.com/repos/consolidation/log/zipball/dfd8189a771fe047bf3cd669111b2de5f1c79395", + "reference": "dfd8189a771fe047bf3cd669111b2de5f1c79395", "shasum": "" }, "require": { @@ -421,8 +318,9 @@ "symfony/console": "^2.8|^3|^4" }, "require-dev": { + "g1a/composer-test-scenarios": "^1", "phpunit/phpunit": "4.*", - "satooshi/php-coveralls": "dev-master", + "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "2.*" }, "type": "library", @@ -447,20 +345,20 @@ } ], "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.", - "time": "2017-11-29T01:44:16+00:00" + "time": "2018-05-25T18:14:39+00:00" }, { "name": "consolidation/output-formatters", - "version": "3.2.0", + "version": "3.2.1", "source": { "type": "git", "url": "https://github.com/consolidation/output-formatters.git", - "reference": "da889e4bce19f145ca4ec5b1725a946f4eb625a9" + "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/da889e4bce19f145ca4ec5b1725a946f4eb625a9", - "reference": "da889e4bce19f145ca4ec5b1725a946f4eb625a9", + "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", + "reference": "d78ef59aea19d3e2e5a23f90a055155ee78a0ad5", "shasum": "" }, "require": { @@ -469,7 +367,7 @@ "symfony/finder": "^2.5|^3|^4" }, "require-dev": { - "g-1-a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^2", "phpunit/phpunit": "^5.7.27", "satooshi/php-coveralls": "^2", "squizlabs/php_codesniffer": "^2.7", @@ -502,25 +400,25 @@ } ], "description": "Format text by applying transformations provided by plug-in formatters.", - "time": "2018-03-20T15:18:32+00:00" + "time": "2018-05-25T18:02:34+00:00" }, { "name": "consolidation/robo", - "version": "1.2.3", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/consolidation/Robo.git", - "reference": "54a13e268917b92576d75e10dca8227b95a574d9" + "reference": "ac563abfadf7cb7314b4e152f2b5033a6c255f6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consolidation/Robo/zipball/54a13e268917b92576d75e10dca8227b95a574d9", - "reference": "54a13e268917b92576d75e10dca8227b95a574d9", + "url": "https://api.github.com/repos/consolidation/Robo/zipball/ac563abfadf7cb7314b4e152f2b5033a6c255f6f", + "reference": "ac563abfadf7cb7314b4e152f2b5033a6c255f6f", "shasum": "" }, "require": { "consolidation/annotated-command": "^2.8.2", - "consolidation/config": "^1.0.1", + "consolidation/config": "^1.0.10", "consolidation/log": "~1", "consolidation/output-formatters": "^3.1.13", "grasmash/yaml-expander": "^1.3", @@ -539,7 +437,7 @@ "codeception/aspect-mock": "^1|^2.1.1", "codeception/base": "^2.3.7", "codeception/verify": "^0.3.2", - "g-1-a/composer-test-scenarios": "^2", + "g1a/composer-test-scenarios": "^2", "goaop/framework": "~2.1.2", "goaop/parser-reflection": "^1.1.0", "natxet/cssmin": "3.0.4", @@ -582,7 +480,7 @@ } ], "description": "Modern task runner", - "time": "2018-04-06T05:27:37+00:00" + "time": "2018-05-27T01:42:53+00:00" }, { "name": "container-interop/container-interop", @@ -674,141 +572,6 @@ ], "time": "2017-01-20T21:14:22+00:00" }, - { - "name": "doctrine/annotations", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Docblock Annotations Parser", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "annotations", - "docblock", - "parser" - ], - "time": "2017-02-24T16:22:25+00:00" - }, - { - "name": "doctrine/collections", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/collections.git", - "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", - "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0" - }, - "require-dev": { - "doctrine/coding-standard": "~0.1@dev", - "phpunit/phpunit": "^5.7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Collections\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Collections Abstraction library", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "array", - "collections", - "iterator" - ], - "time": "2017-01-03T10:49:41+00:00" - }, { "name": "doctrine/instantiator", "version": "1.0.5", @@ -863,136 +626,41 @@ ], "time": "2015-06-14T21:17:01+00:00" }, - { - "name": "doctrine/lexer", - "version": "v1.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", - "shasum": "" - }, - "require": { - "php": ">=5.3.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", - "keywords": [ - "lexer", - "parser" - ], - "time": "2014-09-09T13:34:57+00:00" - }, - { - "name": "epfremme/swagger-php", - "version": "v2.0.0", - "source": { - "type": "git", - "url": "https://github.com/epfremmer/swagger-php.git", - "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/epfremmer/swagger-php/zipball/eee28a442b7e6220391ec953d3c9b936354f23bc", - "reference": "eee28a442b7e6220391ec953d3c9b936354f23bc", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.2", - "doctrine/collections": "^1.3", - "jms/serializer": "^1.1", - "php": ">=5.5", - "phpoption/phpoption": "^1.1", - "symfony/yaml": "^2.7|^3.1" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "~4.8|~5.0", - "satooshi/php-coveralls": "^1.0" - }, - "type": "package", - "autoload": { - "psr-4": { - "Epfremme\\Swagger\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Edward Pfremmer", - "email": "epfremme@nerdery.com" - } - ], - "description": "Library for parsing swagger documentation into PHP entities for use in testing and code generation", - "time": "2016-09-26T17:24:17+00:00" - }, { "name": "facebook/webdriver", - "version": "1.5.0", + "version": "1.6.0", "source": { "type": "git", "url": "https://github.com/facebook/php-webdriver.git", - "reference": "86b5ca2f67173c9d34340845dd690149c886a605" + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/86b5ca2f67173c9d34340845dd690149c886a605", - "reference": "86b5ca2f67173c9d34340845dd690149c886a605", + "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e", + "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e", "shasum": "" }, "require": { "ext-curl": "*", + "ext-json": "*", + "ext-mbstring": "*", "ext-zip": "*", "php": "^5.6 || ~7.0", "symfony/process": "^2.8 || ^3.1 || ^4.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0", - "guzzle/guzzle": "^3.4.1", - "php-coveralls/php-coveralls": "^1.0.2", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "php-coveralls/php-coveralls": "^2.0", "php-mock/php-mock-phpunit": "^1.1", "phpunit/phpunit": "^5.7", "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0", "squizlabs/php_codesniffer": "^2.6", "symfony/var-dumper": "^3.3 || ^4.0" }, + "suggest": { + "ext-SimpleXML": "For Firefox profile creation" + }, "type": "library", "extra": { "branch-alias": { @@ -1016,98 +684,7 @@ "selenium", "webdriver" ], - "time": "2017-11-15T11:08:09+00:00" - }, - { - "name": "flow/jsonpath", - "version": "0.4.0", - "source": { - "type": "git", - "url": "https://github.com/FlowCommunications/JSONPath.git", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/FlowCommunications/JSONPath/zipball/f0222818d5c938e4ab668ab2e2c079bd51a27112", - "reference": "f0222818d5c938e4ab668ab2e2c079bd51a27112", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "peekmo/jsonpath": "dev-master", - "phpunit/phpunit": "^4.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Flow\\JSONPath": "src/", - "Flow\\JSONPath\\Test": "tests/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Stephen Frank", - "email": "stephen@flowsa.com" - } - ], - "description": "JSONPath implementation for parsing, searching and flattening arrays", - "time": "2018-03-04T16:39:47+00:00" - }, - { - "name": "fzaninotto/faker", - "version": "v1.7.1", - "source": { - "type": "git", - "url": "https://github.com/fzaninotto/Faker.git", - "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", - "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "ext-intl": "*", - "phpunit/phpunit": "^4.0 || ^5.0", - "squizlabs/php_codesniffer": "^1.5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Faker\\": "src/Faker/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "François Zaninotto" - } - ], - "description": "Faker is a PHP library that generates fake data for you.", - "keywords": [ - "data", - "faker", - "fixtures" - ], - "time": "2017-08-15T16:48:10+00:00" + "time": "2018-05-16T17:37:13+00:00" }, { "name": "grasmash/expander", @@ -1376,396 +953,53 @@ "description": "PSR-7 message implementation that also provides common utility methods", "keywords": [ "http", - "message", - "request", - "response", - "stream", - "uri", - "url" - ], - "time": "2017-03-20T17:10:46+00:00" - }, - { - "name": "jms/metadata", - "version": "1.6.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/metadata.git", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "doctrine/cache": "~1.0", - "symfony/cache": "~3.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.5.x-dev" - } - }, - "autoload": { - "psr-0": { - "Metadata\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Class/method/property metadata management in PHP", - "keywords": [ - "annotations", - "metadata", - "xml", - "yaml" - ], - "time": "2016-12-05T10:18:33+00:00" - }, - { - "name": "jms/parser-lib", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/parser-lib.git", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/parser-lib/zipball/c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "reference": "c509473bc1b4866415627af0e1c6cc8ac97fa51d", - "shasum": "" - }, - "require": { - "phpoption/phpoption": ">=0.9,<2.0-dev" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "description": "A library for easily creating recursive-descent parsers.", - "time": "2012-11-18T18:08:43+00:00" - }, - { - "name": "jms/serializer", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/serializer.git", - "reference": "e7c53477ff55c21d1b1db7d062edc050a24f465f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/e7c53477ff55c21d1b1db7d062edc050a24f465f", - "reference": "e7c53477ff55c21d1b1db7d062edc050a24f465f", - "shasum": "" - }, - "require": { - "doctrine/annotations": "^1.0", - "doctrine/instantiator": "^1.0.3", - "jms/metadata": "~1.1", - "jms/parser-lib": "1.*", - "php": "^5.5|^7.0", - "phpcollection/phpcollection": "~0.1", - "phpoption/phpoption": "^1.1" - }, - "conflict": { - "twig/twig": "<1.12" - }, - "require-dev": { - "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "^1.3|^2.0", - "ext-pdo_sqlite": "*", - "jackalope/jackalope-doctrine-dbal": "^1.1.5", - "phpunit/phpunit": "^4.8|^5.0", - "propel/propel1": "~1.7", - "psr/container": "^1.0", - "symfony/dependency-injection": "^2.7|^3.3|^4.0", - "symfony/expression-language": "^2.6|^3.0", - "symfony/filesystem": "^2.1", - "symfony/form": "~2.1|^3.0", - "symfony/translation": "^2.1|^3.0", - "symfony/validator": "^2.2|^3.0", - "symfony/yaml": "^2.1|^3.0", - "twig/twig": "~1.12|~2.0" - }, - "suggest": { - "doctrine/cache": "Required if you like to use cache functionality.", - "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", - "symfony/yaml": "Required if you'd like to serialize data to YAML format." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.11-dev" - } - }, - "autoload": { - "psr-0": { - "JMS\\Serializer": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - }, - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", - "homepage": "http://jmsyst.com/libs/serializer", - "keywords": [ - "deserialization", - "jaxb", - "json", - "serialization", - "xml" - ], - "time": "2018-02-04T17:48:54+00:00" - }, - { - "name": "league/container", - "version": "2.4.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/container.git", - "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", - "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", - "shasum": "" - }, - "require": { - "container-interop/container-interop": "^1.2", - "php": "^5.4.0 || ^7.0" - }, - "provide": { - "container-interop/container-interop-implementation": "^1.2", - "psr/container-implementation": "^1.0" - }, - "replace": { - "orno/di": "~2.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev", - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "League\\Container\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Phil Bennett", - "email": "philipobenito@gmail.com", - "homepage": "http://www.philipobenito.com", - "role": "Developer" - } - ], - "description": "A fast and intuitive dependency injection container.", - "homepage": "https://github.com/thephpleague/container", - "keywords": [ - "container", - "dependency", - "di", - "injection", - "league", - "provider", - "service" - ], - "time": "2017-05-10T09:20:27+00:00" - }, - { - "name": "magento/magento2-functional-testing-framework", - "version": "2.2.0", - "source": { - "type": "git", - "url": "git@github.com:magento/magento2-functional-testing-framework.git", - "reference": "4dd196d745bf836cbf0c5904a1df6dd241124309" - }, - "require": { - "allure-framework/allure-codeception": "~1.2.6", - "codeception/codeception": "~2.3.4", - "consolidation/robo": "^1.0.0", - "epfremme/swagger-php": "^2.0", - "flow/jsonpath": ">0.2", - "fzaninotto/faker": "^1.6", - "mustache/mustache": "~2.5", - "php": "7.0.2|7.0.4|~7.0.6|~7.1.0|~7.2.0", - "symfony/process": "^2.8 || ^3.1 || ^4.0", - "vlucas/phpdotenv": "^2.4" - }, - "require-dev": { - "brainmaestro/composer-git-hooks": "^2.3", - "codacy/coverage": "^1.4", - "codeception/aspect-mock": "^2.0", - "goaop/framework": "2.1.2", - "php-coveralls/php-coveralls": "^1.0", - "phpmd/phpmd": "^2.6.0", - "rregeer/phpunit-coverage-check": "^0.1.4", - "sebastian/phpcpd": "~3.0", - "squizlabs/php_codesniffer": "1.5.3", - "symfony/stopwatch": "~3.4.6" - }, - "bin": [ - "bin/mftf" - ], - "type": "library", - "extra": { - "hooks": { - "pre-push": "bin/all-checks" - } - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTestingFramework\\": "src/Magento/FunctionalTestingFramework", - "MFTF\\": "dev/tests/functional/MFTF" - } - }, - "autoload-dev": { - "psr-4": { - "tests\\unit\\": "dev/tests/unit" - } - }, - "scripts": { - "tests": [ - "bin/phpunit-checks" - ], - "static": [ - "bin/static-checks" - ] - }, - "license": [ - "AGPL-3.0" - ], - "description": "Magento2 Functional Testing Framework", - "keywords": [ - "automation", - "functional", - "magento", - "testing" - ], - "time": "2018-04-24T01:46:09+00:00" - }, - { - "name": "moontoast/math", - "version": "1.1.2", - "source": { - "type": "git", - "url": "https://github.com/ramsey/moontoast-math.git", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/moontoast-math/zipball/c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "reference": "c2792a25df5cad4ff3d760dd37078fc5b6fccc79", - "shasum": "" - }, - "require": { - "ext-bcmath": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "jakub-onderka/php-parallel-lint": "^0.9.0", - "phpunit/phpunit": "^4.7|>=5.0 <5.4", - "satooshi/php-coveralls": "^0.6.1", - "squizlabs/php_codesniffer": "^2.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Moontoast\\Math\\": "src/Moontoast/Math/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "A mathematics library, providing functionality for large numbers", - "homepage": "https://github.com/ramsey/moontoast-math", - "keywords": [ - "bcmath", - "math" + "message", + "request", + "response", + "stream", + "uri", + "url" ], - "time": "2017-02-16T16:54:46+00:00" + "time": "2017-03-20T17:10:46+00:00" }, { - "name": "mustache/mustache", - "version": "v2.12.0", + "name": "league/container", + "version": "2.4.1", "source": { "type": "git", - "url": "https://github.com/bobthecow/mustache.php.git", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e" + "url": "https://github.com/thephpleague/container.git", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/mustache.php/zipball/fe8fe72e9d580591854de404cc59a1b83ca4d19e", - "reference": "fe8fe72e9d580591854de404cc59a1b83ca4d19e", + "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0", + "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0", "shasum": "" }, "require": { - "php": ">=5.2.4" + "container-interop/container-interop": "^1.2", + "php": "^5.4.0 || ^7.0" + }, + "provide": { + "container-interop/container-interop-implementation": "^1.2", + "psr/container-implementation": "^1.0" + }, + "replace": { + "orno/di": "~2.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~1.11", - "phpunit/phpunit": "~3.7|~4.0|~5.0" + "phpunit/phpunit": "4.*" }, "type": "library", + "extra": { + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-1.x": "1.x-dev" + } + }, "autoload": { - "psr-0": { - "Mustache": "src/" + "psr-4": { + "League\\Container\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1774,18 +1008,24 @@ ], "authors": [ { - "name": "Justin Hileman", - "email": "justin@justinhileman.info", - "homepage": "http://justinhileman.com" + "name": "Phil Bennett", + "email": "philipobenito@gmail.com", + "homepage": "http://www.philipobenito.com", + "role": "Developer" } ], - "description": "A Mustache implementation in PHP.", - "homepage": "https://github.com/bobthecow/mustache.php", + "description": "A fast and intuitive dependency injection container.", + "homepage": "https://github.com/thephpleague/container", "keywords": [ - "mustache", - "templating" + "container", + "dependency", + "di", + "injection", + "league", + "provider", + "service" ], - "time": "2017-07-11T12:54:05+00:00" + "time": "2017-05-10T09:20:27+00:00" }, { "name": "myclabs/deep-copy", @@ -1832,54 +1072,6 @@ ], "time": "2017-10-19T19:58:43+00:00" }, - { - "name": "paragonie/random_compat", - "version": "v2.0.12", - "source": { - "type": "git", - "url": "https://github.com/paragonie/random_compat.git", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", - "shasum": "" - }, - "require": { - "php": ">=5.2.0" - }, - "require-dev": { - "phpunit/phpunit": "4.*|5.*" - }, - "suggest": { - "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." - }, - "type": "library", - "autoload": { - "files": [ - "lib/random.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Paragon Initiative Enterprises", - "email": "security@paragonie.com", - "homepage": "https://paragonie.com" - } - ], - "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", - "keywords": [ - "csprng", - "pseudorandom", - "random" - ], - "time": "2018-04-04T21:24:14+00:00" - }, { "name": "phar-io/manifest", "version": "1.0.1", @@ -1982,54 +1174,6 @@ "description": "Library for handling version information and constraints", "time": "2017-03-05T17:38:23+00:00" }, - { - "name": "phpcollection/phpcollection", - "version": "0.5.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-collection.git", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-collection/zipball/f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", - "reference": "f2bcff45c0da7c27991bbc1f90f47c4b7fb434a6", - "shasum": "" - }, - "require": { - "phpoption/phpoption": "1.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "0.4-dev" - } - }, - "autoload": { - "psr-0": { - "PhpCollection": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "General-Purpose Collection Library for PHP", - "keywords": [ - "collection", - "list", - "map", - "sequence", - "set" - ], - "time": "2015-05-17T12:39:23+00:00" - }, { "name": "phpdocumentor/reflection-common", "version": "1.0.1", @@ -2182,56 +1326,6 @@ ], "time": "2017-07-14T14:27:02+00:00" }, - { - "name": "phpoption/phpoption", - "version": "1.5.0", - "source": { - "type": "git", - "url": "https://github.com/schmittjoh/php-option.git", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "reference": "94e644f7d2051a5f0fcf77d81605f152eecff0ed", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "4.7.*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "autoload": { - "psr-0": { - "PhpOption\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "Option Type for PHP", - "keywords": [ - "language", - "option", - "php", - "type" - ], - "time": "2015-07-25T16:39:46+00:00" - }, { "name": "phpspec/prophecy", "version": "1.7.6", @@ -2546,16 +1640,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.8", + "version": "6.5.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b" + "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f21a3c6b97c42952fd5c2837bb354ec0199b97b", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/093ca5508174cd8ab8efe44fd1dde447adfdec8f", + "reference": "093ca5508174cd8ab8efe44fd1dde447adfdec8f", "shasum": "" }, "require": { @@ -2626,20 +1720,20 @@ "testing", "xunit" ], - "time": "2018-04-10T11:38:34+00:00" + "time": "2018-07-03T06:40:40+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "5.0.6", + "version": "5.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + "reference": "6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f", + "reference": "6f9a3c8bf34188a2b53ce2ae7a126089c53e0a9f", "shasum": "" }, "require": { @@ -2685,7 +1779,7 @@ "mock", "xunit" ], - "time": "2018-01-06T05:45:45+00:00" + "time": "2018-07-13T03:27:23+00:00" }, { "name": "psr/container", @@ -2833,86 +1927,6 @@ ], "time": "2016-10-10T12:19:37+00:00" }, - { - "name": "ramsey/uuid", - "version": "3.7.3", - "source": { - "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/44abcdad877d9a46685a3a4d221e3b2c4b87cb76", - "reference": "44abcdad877d9a46685a3a4d221e3b2c4b87cb76", - "shasum": "" - }, - "require": { - "paragonie/random_compat": "^1.0|^2.0", - "php": "^5.4 || ^7.0" - }, - "replace": { - "rhumsaa/uuid": "self.version" - }, - "require-dev": { - "codeception/aspect-mock": "^1.0 | ~2.0.0", - "doctrine/annotations": "~1.2.0", - "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1", - "ircmaxell/random-lib": "^1.1", - "jakub-onderka/php-parallel-lint": "^0.9.0", - "mockery/mockery": "^0.9.9", - "moontoast/math": "^1.1", - "php-mock/php-mock-phpunit": "^0.3|^1.1", - "phpunit/phpunit": "^4.7|^5.0", - "squizlabs/php_codesniffer": "^2.3" - }, - "suggest": { - "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator", - "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator", - "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).", - "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Ramsey\\Uuid\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marijn Huizendveld", - "email": "marijn.huizendveld@gmail.com" - }, - { - "name": "Thibaud Fabre", - "email": "thibaud@aztech.io" - }, - { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" - } - ], - "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).", - "homepage": "https://github.com/ramsey/uuid", - "keywords": [ - "guid", - "identifier", - "uuid" - ], - "time": "2018-01-20T00:28:24+00:00" - }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -3474,16 +2488,16 @@ }, { "name": "symfony/browser-kit", - "version": "v3.4.9", + "version": "v3.4.14", "source": { "type": "git", "url": "https://github.com/symfony/browser-kit.git", - "reference": "840bb6f0d5b3701fd768b68adf7193c2d0f98f79" + "reference": "f6668d1a6182d5a8dec65a1c863a4c1d963816c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/browser-kit/zipball/840bb6f0d5b3701fd768b68adf7193c2d0f98f79", - "reference": "840bb6f0d5b3701fd768b68adf7193c2d0f98f79", + "url": "https://api.github.com/repos/symfony/browser-kit/zipball/f6668d1a6182d5a8dec65a1c863a4c1d963816c0", + "reference": "f6668d1a6182d5a8dec65a1c863a4c1d963816c0", "shasum": "" }, "require": { @@ -3527,20 +2541,20 @@ ], "description": "Symfony BrowserKit Component", "homepage": "https://symfony.com", - "time": "2018-03-19T22:32:39+00:00" + "time": "2018-07-26T09:06:28+00:00" }, { "name": "symfony/console", - "version": "v3.4.9", + "version": "v3.4.14", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5b1fdfa8eb93464bcc36c34da39cedffef822cdf" + "reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5b1fdfa8eb93464bcc36c34da39cedffef822cdf", - "reference": "5b1fdfa8eb93464bcc36c34da39cedffef822cdf", + "url": "https://api.github.com/repos/symfony/console/zipball/6b217594552b9323bcdcfc14f8a0ce126e84cd73", + "reference": "6b217594552b9323bcdcfc14f8a0ce126e84cd73", "shasum": "" }, "require": { @@ -3596,20 +2610,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-04-30T01:22:56+00:00" + "time": "2018-07-26T11:19:56+00:00" }, { "name": "symfony/css-selector", - "version": "v3.4.9", + "version": "v3.4.14", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "519a80d7c1d95c6cc0b67f686d15fe27c6910de0" + "reference": "edda5a6155000ff8c3a3f85ee5c421af93cca416" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/519a80d7c1d95c6cc0b67f686d15fe27c6910de0", - "reference": "519a80d7c1d95c6cc0b67f686d15fe27c6910de0", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/edda5a6155000ff8c3a3f85ee5c421af93cca416", + "reference": "edda5a6155000ff8c3a3f85ee5c421af93cca416", "shasum": "" }, "require": { @@ -3649,20 +2663,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2018-03-19T22:32:39+00:00" + "time": "2018-07-26T09:06:28+00:00" }, { "name": "symfony/debug", - "version": "v3.4.9", + "version": "v3.4.14", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "1b95888cfd996484527cb41e8952d9a5eaf7454f" + "reference": "d5a058ff6ecad26b30c1ba452241306ea34c65cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/1b95888cfd996484527cb41e8952d9a5eaf7454f", - "reference": "1b95888cfd996484527cb41e8952d9a5eaf7454f", + "url": "https://api.github.com/repos/symfony/debug/zipball/d5a058ff6ecad26b30c1ba452241306ea34c65cc", + "reference": "d5a058ff6ecad26b30c1ba452241306ea34c65cc", "shasum": "" }, "require": { @@ -3705,24 +2719,25 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-04-30T16:53:52+00:00" + "time": "2018-07-26T11:19:56+00:00" }, { "name": "symfony/dom-crawler", - "version": "v3.4.9", + "version": "v3.4.14", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "1a4cffeb059226ff6bee9f48acb388faf674afff" + "reference": "452bfc854b60134438e3824b159b0d24a5892331" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/1a4cffeb059226ff6bee9f48acb388faf674afff", - "reference": "1a4cffeb059226ff6bee9f48acb388faf674afff", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/452bfc854b60134438e3824b159b0d24a5892331", + "reference": "452bfc854b60134438e3824b159b0d24a5892331", "shasum": "" }, "require": { "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0" }, "require-dev": { @@ -3761,20 +2776,20 @@ ], "description": "Symfony DomCrawler Component", "homepage": "https://symfony.com", - "time": "2018-03-19T22:32:39+00:00" + "time": "2018-07-26T10:03:52+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v3.4.9", + "version": "v3.4.14", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8" + "reference": "b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fdd5abcebd1061ec647089c6c41a07ed60af09f8", - "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb", + "reference": "b2e1f19280c09a42dc64c0b72b80fe44dd6e88fb", "shasum": "" }, "require": { @@ -3824,24 +2839,25 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06T07:35:25+00:00" + "time": "2018-07-26T09:06:28+00:00" }, { "name": "symfony/filesystem", - "version": "v3.4.9", + "version": "v3.4.12", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "253a4490b528597aa14d2bf5aeded6f5e5e4a541" + "reference": "8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/253a4490b528597aa14d2bf5aeded6f5e5e4a541", - "reference": "253a4490b528597aa14d2bf5aeded6f5e5e4a541", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed", + "reference": "8a721a5f2553c6c3482b1c5b22ed60fe94dd63ed", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { @@ -3873,20 +2889,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2018-02-22T10:48:49+00:00" + "time": "2018-06-21T11:10:19+00:00" }, { "name": "symfony/finder", - "version": "v3.4.9", + "version": "v3.4.12", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "bd14efe8b1fabc4de82bf50dce62f05f9a102433" + "reference": "3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/bd14efe8b1fabc4de82bf50dce62f05f9a102433", - "reference": "bd14efe8b1fabc4de82bf50dce62f05f9a102433", + "url": "https://api.github.com/repos/symfony/finder/zipball/3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394", + "reference": "3a8c3de91d2b2c68cd2d665cf9d00f7ef9eaa394", "shasum": "" }, "require": { @@ -3922,42 +2938,37 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-04-04T05:07:11+00:00" + "time": "2018-06-19T20:52:10+00:00" }, { - "name": "symfony/http-foundation", - "version": "v3.4.9", + "name": "symfony/polyfill-ctype", + "version": "v1.8.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "edc43b1a50402bb06b5111eb86b275c87a93e373" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/edc43b1a50402bb06b5111eb86b275c87a93e373", - "reference": "edc43b1a50402bb06b5111eb86b275c87a93e373", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-mbstring": "~1.1", - "symfony/polyfill-php70": "~1.6" - }, - "require-dev": { - "symfony/expression-language": "~2.8|~3.0|~4.0" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "1.8-dev" } }, "autoload": { "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" + "Symfony\\Polyfill\\Ctype\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "bootstrap.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3965,18 +2976,24 @@ "MIT" ], "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" + }, + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" } ], - "description": "Symfony HttpFoundation Component", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", - "time": "2018-04-30T01:05:13+00:00" + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "time": "2018-04-30T19:57:29+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -4037,77 +3054,18 @@ ], "time": "2018-04-26T10:06:28+00:00" }, - { - "name": "symfony/polyfill-php70", - "version": "v1.8.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/77454693d8f10dd23bb24955cffd2d82db1007a6", - "reference": "77454693d8f10dd23bb24955cffd2d82db1007a6", - "shasum": "" - }, - "require": { - "paragonie/random_compat": "~1.0|~2.0", - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.8-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php70\\": "" - }, - "files": [ - "bootstrap.php" - ], - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "time": "2018-04-26T10:06:28+00:00" - }, { "name": "symfony/process", - "version": "v3.4.9", + "version": "v3.4.12", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "4b7d64e852886319e93ddfdecff0d744ab87658b" + "reference": "acc5a37c706ace827962851b69705b24e71ca17c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/4b7d64e852886319e93ddfdecff0d744ab87658b", - "reference": "4b7d64e852886319e93ddfdecff0d744ab87658b", + "url": "https://api.github.com/repos/symfony/process/zipball/acc5a37c706ace827962851b69705b24e71ca17c", + "reference": "acc5a37c706ace827962851b69705b24e71ca17c", "shasum": "" }, "require": { @@ -4143,24 +3101,25 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-04-03T05:22:50+00:00" + "time": "2018-05-30T04:24:30+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.9", + "version": "v3.4.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "033cfa61ef06ee0847e056e530201842b6e926c3" + "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/033cfa61ef06ee0847e056e530201842b6e926c3", - "reference": "033cfa61ef06ee0847e056e530201842b6e926c3", + "url": "https://api.github.com/repos/symfony/yaml/zipball/c5010cc1692ce1fa328b1fb666961eb3d4a85bb0", + "reference": "c5010cc1692ce1fa328b1fb666961eb3d4a85bb0", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": "^5.5.9|>=7.0.8", + "symfony/polyfill-ctype": "~1.8" }, "conflict": { "symfony/console": "<3.4" @@ -4201,7 +3160,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-04-08T08:21:29+00:00" + "time": "2018-05-03T23:18:14+00:00" }, { "name": "theseer/tokenizer", @@ -4245,28 +3204,28 @@ }, { "name": "vlucas/phpdotenv", - "version": "v2.4.0", + "version": "v2.5.1", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c" + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", - "reference": "3cc116adbe4b11be5ec557bf1d24dc5e3a21d18c", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", + "reference": "8abb4f9aa89ddea9d52112c65bbe8d0125e2fa8e", "shasum": "" }, "require": { "php": ">=5.3.9" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" + "phpunit/phpunit": "^4.8.35 || ^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "2.5-dev" } }, "autoload": { @@ -4276,7 +3235,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause-Attribution" + "BSD-3-Clause" ], "authors": [ { @@ -4291,7 +3250,7 @@ "env", "environment" ], - "time": "2016-09-01T10:05:43+00:00" + "time": "2018-07-29T20:33:41+00:00" }, { "name": "webmozart/assert", diff --git a/dev/tests/acceptance/pre-install.php b/dev/tests/acceptance/pre-install.php deleted file mode 100644 index 4021dc1094948..0000000000000 --- a/dev/tests/acceptance/pre-install.php +++ /dev/null @@ -1,395 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -/** - * @codingStandardsIgnoreStart - * @SuppressWarnings(PHPMD) - */ -class CliColors { - private $foreground_colors = array(); - private $background_colors = array(); - - public function __construct() { - // Set up shell colors - $this->foreground_colors['black'] = '0;30'; - $this->foreground_colors['dark_gray'] = '1;30'; - $this->foreground_colors['blue'] = '0;34'; - $this->foreground_colors['light_blue'] = '1;34'; - $this->foreground_colors['green'] = '0;32'; - $this->foreground_colors['light_green'] = '1;32'; - $this->foreground_colors['cyan'] = '0;36'; - $this->foreground_colors['light_cyan'] = '1;36'; - $this->foreground_colors['red'] = '0;31'; - $this->foreground_colors['light_red'] = '1;31'; - $this->foreground_colors['purple'] = '0;35'; - $this->foreground_colors['light_purple'] = '1;35'; - $this->foreground_colors['brown'] = '0;33'; - $this->foreground_colors['yellow'] = '1;33'; - $this->foreground_colors['light_gray'] = '0;37'; - $this->foreground_colors['white'] = '1;37'; - - $this->background_colors['black'] = '40'; - $this->background_colors['red'] = '41'; - $this->background_colors['green'] = '42'; - $this->background_colors['yellow'] = '43'; - $this->background_colors['blue'] = '44'; - $this->background_colors['magenta'] = '45'; - $this->background_colors['cyan'] = '46'; - $this->background_colors['light_gray'] = '47'; - } - - /** - * Returns colored string - * - * @param $string - * @param null $foreground_color - * @param null $background_color - * @return string - */ - public function getColoredString($string, $foreground_color = null, $background_color = null) { - $colored_string = ""; - - // Check if given foreground color found - if (isset($this->foreground_colors[$foreground_color])) { - $colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m"; - } - // Check if given background color found - if (isset($this->background_colors[$background_color])) { - $colored_string .= "\033[" . $this->background_colors[$background_color] . "m"; - } - - // Add string and end coloring - $colored_string .= $string . "\033[0m"; - - return $colored_string; - } - - /** - * Returns all foreground color names - * - * @return array - */ - public function getForegroundColors() { - return array_keys($this->foreground_colors); - } - - /** - * Returns all background color names - * - * @return array - */ - public function getBackgroundColors() { - return array_keys($this->background_colors); - } -} - -/** - * @SuppressWarnings(PHPMD) - */ -class PreInstallCheck { - private $installedViaBrew = false; - private $filePath = ''; - private $seleniumJarVersion = ''; - - private $phpWebsite = 'http://php.net/manual/en/install.php'; - private $composerWebsite = 'https://getcomposer.org/download/'; - private $javaWebsite = 'https://www.java.com/en/download/'; - private $allureCliWebsite = 'https://docs.qameta.io/allure/latest/#_installing_a_commandline'; - private $seleniumWebsite = 'http://www.seleniumhq.org/download/'; - private $chromeDriverWebsite = 'https://sites.google.com/a/chromium.org/chromedriver/downloads'; - private $geckoDriverWebsite = 'https://github.com/mozilla/geckodriver'; - private $phantomJsWebsite = 'http://phantomjs.org/'; - - private $phpSupportedVersion = '7.1.0'; - private $composerSupportedVersion = '1.3.0'; - private $javaSupportedVersion = '1.8.0'; - private $allureCliSupportedVersion = '2.3.0'; - private $seleniumSupportedVersion = '3.6.0'; - private $chromeDriverSupportedVersion = '2.33.0'; - private $geckoDriverSupportedVersion = '0.19.0'; - private $phantomJsSupportedVersion = '2.1.0'; - - private $getPhpVersion; - private $getComposerVersion; - private $getJavaVersion; - private $getAllureCliVersion; - private $getSeleniumVersion; - private $getChromeDriverVersion; - private $getGeckoDriverVersion; - private $getPhantomJsVersion; - - private $phpVersion; - private $composerVersion; - private $javaVersion; - private $allureCliVersion; - private $seleniumVersion; - private $chromeDriverVersion; - private $geckoDriverVersion; - private $phantomJsVersion; - - private $phpStatus; - private $composerStatus; - private $javaStatus; - private $allureCliStatus; - private $seleniumStatus; - private $chromeDriverStatus; - private $geckoDriverStatus; - private $phantomJsStatus; - - function __construct() { - $this->didYouInstallViaBrew(); - - $this->getPhpVersion = shell_exec('php --version'); - $this->getComposerVersion = shell_exec('composer --version'); - $this->getJavaVersion = shell_exec("java -version 2>&1"); - $this->getAllureCliVersion = shell_exec('allure --version'); - $this->getSeleniumVersion = $this->getSeleniumVersion(); - $this->getChromeDriverVersion = $this->getChromeDriverVersion(); - $this->getGeckoDriverVersion = shell_exec('geckodriver --version'); - $this->getPhantomJsVersion = $this->getPhantomJsVersion(); - - $this->phpVersion = $this->parseVersion($this->getPhpVersion); - $this->composerVersion = $this->parseVersion($this->getComposerVersion); - $this->javaVersion = $this->parseJavaVersion($this->getJavaVersion); - $this->allureCliVersion = $this->parseVersion($this->getAllureCliVersion); - $this->seleniumVersion = $this->parseVersion($this->getSeleniumVersion); - $this->chromeDriverVersion = $this->parseVersion($this->getChromeDriverVersion); - $this->geckoDriverVersion = $this->parseVersion($this->getGeckoDriverVersion); - $this->phantomJsVersion = $this->parseVersion($this->getPhantomJsVersion); - - // String of null Versions - For Testing -// $this->phpVersion = null; -// $this->composerVersion = null; -// $this->javaVersion = null; -// $this->allureCliVersion = null; -// $this->seleniumVersion = null; -// $this->chromeDriverVersion = null; -// $this->geckoDriverVersion = null; -// $this->phantomJsVersion = null; - - // String of invalid Versions - For Testing -// $this->phpVersion = '7.0.0'; -// $this->composerVersion = '1.0.0'; -// $this->javaVersion = '1.0.0'; -// $this->allureCliVersion = '2.0.0'; -// $this->seleniumVersion = '3.0.0'; -// $this->chromeDriverVersion = '2.0.0'; -// $this->geckoDriverVersion = '0.0.0'; -// $this->phantomJsVersion = '2.0.0'; - - $this->phpStatus = $this->verifyVersion('PHP', $this->phpVersion, $this->phpSupportedVersion, $this->phpWebsite); - $this->composerStatus = $this->verifyVersion('Composer', $this->composerVersion, $this->composerSupportedVersion, $this->composerWebsite); - $this->javaStatus = $this->verifyVersion('Java', $this->javaVersion, $this->javaSupportedVersion, $this->javaWebsite); - $this->allureCliStatus = $this->verifyVersion('Allure CLI', $this->allureCliVersion, $this->allureCliSupportedVersion, $this->allureCliWebsite); - $this->seleniumStatus = $this->verifyVersion('Selenium Standalone Server', $this->seleniumVersion, $this->seleniumSupportedVersion, $this->seleniumWebsite); - $this->chromeDriverStatus = $this->verifyVersion('ChromeDriver', $this->chromeDriverVersion, $this->chromeDriverSupportedVersion, $this->chromeDriverWebsite); - $this->geckoDriverStatus = $this->verifyVersion('GeckoDriver', $this->geckoDriverVersion, $this->geckoDriverSupportedVersion, $this->geckoDriverWebsite); - $this->phantomJsStatus = $this->verifyVersion('PhantomJS', $this->phantomJsVersion, $this->phantomJsSupportedVersion, $this->phantomJsWebsite); - - ECHO "\n"; - $mask = "|%-13.13s |%18.18s |%18.18s |%-23.23s |\n"; - printf("---------------------------------------------------------------------------------\n"); - printf($mask, ' Software', 'Supported Version', 'Installed Version', ' Status'); - printf("---------------------------------------------------------------------------------\n"); - printf($mask, ' PHP', $this->phpSupportedVersion . '+', $this->phpVersion, ' ' . $this->phpStatus); - printf($mask, ' Composer', $this->composerSupportedVersion . '+', $this->composerVersion, ' ' . $this->composerStatus); - printf($mask, ' Java', $this->javaSupportedVersion . '+', $this->javaVersion, ' ' . $this->javaStatus); - printf($mask, ' Allure CLI', $this->allureCliSupportedVersion . '+', $this->allureCliVersion, ' ' . $this->allureCliStatus); - printf($mask, ' Selenium', $this->seleniumSupportedVersion . '+', $this->seleniumVersion, ' ' . $this->seleniumStatus); - printf($mask, ' ChromeDriver', $this->chromeDriverSupportedVersion . '+', $this->chromeDriverVersion, ' ' . $this->chromeDriverStatus); - printf($mask, ' GeckoDriver', $this->geckoDriverSupportedVersion . '+', $this->geckoDriverVersion, ' ' . $this->geckoDriverStatus); - printf($mask, ' PhantomJS', $this->phantomJsSupportedVersion . '+', $this->phantomJsVersion, ' ' . $this->phantomJsStatus); - printf("---------------------------------------------------------------------------------\n"); - } - - /** - * Ask if they installed the Browser Drivers via Brew. - * Brew installs things globally making them easier for us to access. - */ - public function didYouInstallViaBrew() - { - ECHO "Did you install Selenium Server, ChromeDriver, GeckoDriver and PhantomJS using Brew? (y/n) "; - $handle1 = fopen ("php://stdin","r"); - $line1 = fgets($handle1); - if (trim($line1) != 'y') { - ECHO "Where did you save the files? (ex /Users/first_last/Automation/) "; - $handle2 = fopen ("php://stdin","r"); - $this->filePath = fgets($handle2); - fclose($handle2); - - ECHO "Which selenium-server-standalone-X.X.X.jar file did you download? (ex 3.6.0) "; - $handle3 = fopen ("php://stdin","r"); - $this->seleniumJarVersion = fgets($handle3); - fclose($handle3); - fclose($handle1); - ECHO "\n"; - } else { - $this->installedViaBrew = true; - fclose($handle1); - ECHO "\n"; - } - } - - /** - * Parse the string that is returned for the Version number only. - * - * @param $stdout - * @return null - */ - public function parseVersion($stdout) - { - preg_match("/\d+(?:\.\d+)+/", $stdout, $matches); - - if (!is_null($matches) && isset($matches[0])) { - return $matches[0]; - } else { - return null; - } - } - - /** - * Parse the string that is returned for the Version number only. - * The message Java returns differs from the others hence the separate function. - * - * @param $stdout - * @return null - */ - public function parseJavaVersion($stdout) - { - preg_match('/\"(.+?)\"/', $stdout, $output_array); - - if (!is_null($output_array)) { - return $output_array[1]; - } else { - return null; - } - } - - /** - * Get the Selenium Server version based on how it was installed. - * - * @return string - */ - public function getSeleniumVersion() - { - $this->installedViaBrew; - $this->filePath; - $this->seleniumJarVersion; - - if ($this->installedViaBrew) { - return shell_exec('selenium-server --version'); - } else { - $command = sprintf('java -jar %s/selenium-server-standalone-%s.jar --version', $this->filePath, $this->seleniumJarVersion); - $command = str_replace(array("\r", "\n"), '', $command) . "\n"; - return shell_exec($command); - } - } - - /** - * Get the ChromeDriver version based on how it was installed. - * - * @return string - */ - public function getChromeDriverVersion() - { - $this->installedViaBrew; - $this->filePath; - - if ($this->installedViaBrew) { - return shell_exec('chromedriver --version'); - } else { - $command = sprintf('%s/chromedriver --version', $this->filePath); - $command = str_replace(array("\r", "\n"), '', $command) . "\n"; - return shell_exec($command); - } - } - - /** - * Get the PhantomJS version based on how it was installed. - * - * @return string - */ - public function getPhantomJsVersion() - { - $this->installedViaBrew; - $this->filePath; - - if ($this->installedViaBrew) { - return shell_exec('phantomjs --version'); - } else { - $command = sprintf('%s/phantomjs --version', $this->filePath); - $command = str_replace(array("\r", "\n"), '', $command) . "\n"; - return shell_exec($command); - } - } - - /** - * Print a "Valid Version Detected" message in color. - * - * @param $softwareName - */ - public function printValidVersion($softwareName) - { - $colors = new CliColors(); - $string = sprintf("%s detected. Version is supported!", $softwareName); - ECHO $colors->getColoredString($string, "black", "green") . "\n"; - } - - /** - * Print a "Upgraded Version Needed" message in color. - * - * @param $softwareName - * @param $supportedVersion - * @param $website - */ - public function printUpgradeVersion($softwareName, $supportedVersion, $website) - { - $colors = new CliColors(); - $string = sprintf("Unsupported version of %s detected. Please upgrade to v%s+: %s", $softwareName, $supportedVersion, $website); - ECHO $colors->getColoredString($string, "black", "yellow") . "\n"; - } - - /** - * Print a "Not Installed. Install Required." message in color. - * - * @param $softwareName - * @param $supportedVersion - * @param $website - */ - public function printNoInstalledVersion($softwareName, $supportedVersion, $website) - { - $colors = new CliColors(); - $string = sprintf("%s not detected. Please install v%s+: %s", $softwareName, $supportedVersion, $website); - ECHO $colors->getColoredString($string, "black", "red") . "\n"; - } - - /** - * Verify that the versions. - * Print the correct status message. - * - * @param $softwareName - * @param $installedVersion - * @param $supportedVersion - * @param $website - * @return string - */ - public function verifyVersion($softwareName, $installedVersion, $supportedVersion, $website) - { - if (is_null($installedVersion)) { - $this->printNoInstalledVersion($softwareName, $supportedVersion, $website); - return 'Installation Required!'; - } else if ($installedVersion >= $supportedVersion) { - $this->printValidVersion($softwareName); - return 'Correct Version!'; - } else { - $this->printUpgradeVersion($softwareName, $supportedVersion, $website); - return 'Upgrade Required!'; - } - } -} - -$preCheck = new PreInstallCheck(); -// @codingStandardsIgnoreEnd \ No newline at end of file diff --git a/dev/tests/acceptance/tests/_bootstrap.php b/dev/tests/acceptance/tests/_bootstrap.php index d15481d18098c..2fa2da03d8f8b 100644 --- a/dev/tests/acceptance/tests/_bootstrap.php +++ b/dev/tests/acceptance/tests/_bootstrap.php @@ -4,107 +4,6 @@ * See COPYING.txt for license details. */ -$bootstrapRoot = dirname(__DIR__); -$projectRootPath = $bootstrapRoot; - -// If we're under a vendor directory, find project root. -if (strpos($bootstrapRoot, '/vendor') !== false) { - $projectRootPath = substr($bootstrapRoot, 0, strpos($bootstrapRoot, '/vendor/')); -} - -define('PROJECT_ROOT', $projectRootPath); - - -//Load base paths from composer autoload file -$autoloadFile = PROJECT_ROOT . '/vendor/autoload.php'; - -$loader = require $autoloadFile; - -// Package Names. -$FW_PACKAGE_NAME = "Magento\FunctionalTestingFramework\\"; -$TESTS_PACKAGE_NAME = "TODO"; -$MAGENTO_PACKAGE_NAME = "Magento\\"; - -// Find framework path -$COMPOSER_FW_FULL_PREFIX = $loader->getPrefixesPsr4()[$FW_PACKAGE_NAME][0] ?? null; -if ($COMPOSER_FW_FULL_PREFIX === null) { - throw new Exception( - "You must have the magento/magento2-functional-testing-framework - installed to be able to generate tests." - ); -} -$FW_PATH = substr( - $COMPOSER_FW_FULL_PREFIX, - 0, - strpos($COMPOSER_FW_FULL_PREFIX, "/src/Magento/FunctionalTestingFramework") -); - -// Find tests path -$COMPOSER_TEST_FULL_PREFIX = $loader->getPrefixesPsr4()[$TESTS_PACKAGE_NAME][0] ?? null; -if ($COMPOSER_TEST_FULL_PREFIX === null) { - $TEST_PATH = __DIR__ . "/functional"; -} else { - // Can't determine what to trim; we don't know the package name/structure yet - $TEST_PATH = $COMPOSER_TEST_FULL_PREFIX; -} - -// We register "Magento\\" to "tests/functional/Magento" for our own class loading, need to try and find a -// prefix that isn't that one. -$COMPOSER_MAGENTO_PREFIXES = $loader->getPrefixesPsr4()[$MAGENTO_PACKAGE_NAME]; -$COMPOSER_MAGENTO_FULL_PREFIX = null; -foreach ($COMPOSER_MAGENTO_PREFIXES as $path) { - if (strpos($path, "tests/functional/Magento") === 0) { - $COMPOSER_MAGENTO_FULL_PREFIX = $path; - } -} -if ($COMPOSER_MAGENTO_FULL_PREFIX === null) { - $MAGENTO_PATH = dirname(__DIR__ . "/../../../../../"); -} else { - $MAGENTO_PATH = substr( - $COMPOSER_MAGENTO_FULL_PREFIX, - 0, - strpos($COMPOSER_MAGENTO_FULL_PREFIX, "/app/code/Magento") - ); -} - -$RELATIVE_TESTS_MODULE_PATH = '/Magento/FunctionalTest'; - -defined('MAGENTO_BP') || define('MAGENTO_BP', realpath($MAGENTO_PATH)); - -//Load constants from .env file -if (file_exists(MAGENTO_BP . '/dev/tests/acceptance/.env')) { - $env = new \Dotenv\Loader(MAGENTO_BP . '/dev/tests/acceptance/.env'); - $env->load(); - - if (array_key_exists('TESTS_MODULE_PATH', $_ENV) xor array_key_exists('TESTS_BP', $_ENV)) { - throw new Exception( - 'You must define both parameters TESTS_BP and TESTS_MODULE_PATH or neither parameter' - ); - } - - foreach ($_ENV as $key => $var) { - defined($key) || define($key, $var); - } - - defined('MAGENTO_CLI_COMMAND_PATH') || define( - 'MAGENTO_CLI_COMMAND_PATH', - 'dev/tests/acceptance/utils/command.php' - ); - $env->setEnvironmentVariable('MAGENTO_CLI_COMMAND_PATH', MAGENTO_CLI_COMMAND_PATH); - - defined('MAGENTO_CLI_COMMAND_PARAMETER') || define('MAGENTO_CLI_COMMAND_PARAMETER', 'command'); - $env->setEnvironmentVariable('MAGENTO_CLI_COMMAND_PARAMETER', MAGENTO_CLI_COMMAND_PARAMETER); -} - -defined('FW_BP') || define('FW_BP', realpath($FW_PATH)); -defined('TESTS_BP') || define('TESTS_BP', realpath($TEST_PATH)); -defined('TESTS_MODULE_PATH') || define( - 'TESTS_MODULE_PATH', - realpath($TEST_PATH . $RELATIVE_TESTS_MODULE_PATH) -); - -// add the debug flag here -$debug_mode = $_ENV['MFTF_DEBUG'] ?? false; -if (!(bool)$debug_mode && extension_loaded('xdebug')) { - xdebug_disable(); -} +//TODO remove this file once MFTF is fully decoupled from Magento +// Need to load in the root level autload file +require_once realpath(dirname(__DIR__) . "/../../../vendor/autoload.php"); diff --git a/dev/tests/acceptance/tests/functional.suite.dist.yml b/dev/tests/acceptance/tests/functional.suite.dist.yml deleted file mode 100644 index e01c46ea7c649..0000000000000 --- a/dev/tests/acceptance/tests/functional.suite.dist.yml +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright © Magento, Inc. All rights reserved. -# See COPYING.txt for license details. - -# Codeception Test Suite Configuration -# -# Suite for acceptance tests. -# Perform tests in browser using the WebDriver or PhpBrowser. -# If you need both WebDriver and PHPBrowser tests - create a separate suite. - -class_name: AcceptanceTester -namespace: Magento\FunctionalTestingFramework -modules: - enabled: - - \Magento\FunctionalTestingFramework\Module\MagentoWebDriver - - \Magento\FunctionalTestingFramework\Helper\Acceptance - - \Magento\FunctionalTestingFramework\Helper\MagentoFakerData - - \Magento\FunctionalTestingFramework\Module\MagentoRestDriver: - url: "%MAGENTO_BASE_URL%/rest/default/V1/" - username: "%MAGENTO_ADMIN_USERNAME%" - password: "%MAGENTO_ADMIN_PASSWORD%" - depends: PhpBrowser - part: Json - - \Magento\FunctionalTestingFramework\Module\MagentoSequence - - \Magento\FunctionalTestingFramework\Module\MagentoAssert - - Asserts - config: - \Magento\FunctionalTestingFramework\Module\MagentoWebDriver: - url: "%MAGENTO_BASE_URL%" - backend_name: "%MAGENTO_BACKEND_NAME%" - browser: 'chrome' - window_size: maximize - username: "%MAGENTO_ADMIN_USERNAME%" - password: "%MAGENTO_ADMIN_PASSWORD%" - pageload_timeout: 30 - host: %SELENIUM_HOST% - port: %SELENIUM_PORT% - protocol: %SELENIUM_PROTOCOL% - path: %SELENIUM_PATH% - capabilities: - chromeOptions: - args: ["--start-maximized", "--disable-extensions", "--enable-automation"] - diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/LICENSE.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/LICENSE.txt deleted file mode 100644 index 49525fd99da9c..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Open Software License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/LICENSE_AFL.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Academic Free License ("AFL") v. 3.0 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Academic Free License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/README.md b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/README.md deleted file mode 100644 index 1f48eef7f97a7..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Magento 2 Functional Tests - -The Functional Tests Module for **Magento_GraphQl** Module. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/composer.json deleted file mode 100644 index e2562cb481eb1..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/GraphQl/composer.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "magento/magento2-functional-test-module-graph-ql", - "description": "Magento 2 Functional Test Module Graph Ql", - "type": "magento2-test-module", - "version": "100.0.0-dev", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "1.0.0", - "php": "~7.0.13|~7.1.0" - }, - "suggest": { - "magento/magento2-functional-test-module-webapi": "100.0.0-dev", - "magento/magento2-functional-test-module-eav": "100.0.0-dev", - "magento/magento2-functional-test-module-catalog": "100.0.0-dev" - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTest\\GraphQl\\": "" - } - }, - "extra": { - "map": [ - [ - "*", - "tests/functional/Magento/FunctionalTest/GraphQl" - ] - ] - } -} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/ActionGroup/TemplateActionGroupFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/ActionGroup/TemplateActionGroupFile.xml deleted file mode 100644 index d1bc251f26321..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/ActionGroup/TemplateActionGroupFile.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name=""> - <!-- ADD TEST STEPS HERE --> - </actionGroup> -</actionGroups> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Data/TemplateDataFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Data/TemplateDataFile.xml deleted file mode 100644 index 6f13f04aef31d..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Data/TemplateDataFile.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="" type=""> - <data key=""></data> - </entity> -</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE.txt deleted file mode 100644 index 49525fd99da9c..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Open Software License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE_AFL.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Academic Free License ("AFL") v. 3.0 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Academic Free License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Metadata/TemplateMetaFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Metadata/TemplateMetaFile.xml deleted file mode 100644 index 7293cea9a9b83..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Metadata/TemplateMetaFile.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<operations xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataOperation.xsd"> - <operation name="" dataType="" type="" auth="adminOauth" url="" method=""> - <contentType>application/json</contentType> - <param key=""></param> - <object dataType="" key=""> - <field key=""></field> - <array key=""> - <value></value> - </array> - </object> - <field key=""></field> - </operation> -</operations> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/README.md b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/README.md deleted file mode 100644 index 986c2af6164e9..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Magento 2 Functional Tests - -The Functional Tests Module for **Magento_SampleTemplates** Module. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Test/TemplateTestFile.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Test/TemplateTestFile.xml deleted file mode 100644 index 3aabb6dcb6eb7..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/Test/TemplateTestFile.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="templateTestFile"> - <annotations> - <features value=""/> - <stories value=""/> - </annotations> - <before> - - </before> - <after> - - </after> - <!-- ADD TEST STEPS HERE --> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/composer.json deleted file mode 100644 index 4f6018c01fb16..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTemplates/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "magento/magento2-functional-test-module-sample-templates", - "description": "Magento 2 Functional Test Module Sample Templates", - "type": "magento2-test-module", - "version": "100.0.0-dev", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "1.0.0", - "php": "~7.0.13|~7.1.0" - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTest\\SampleTemplates\\": "" - } - }, - "extra": { - "map": [ - [ - "*", - "tests/functional/Magento/FunctionalTest/SampleTemplates" - ] - ] - } -} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/ActionGroup/SampleActionGroup.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/ActionGroup/SampleActionGroup.xml deleted file mode 100644 index 45ec5f3adf218..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/ActionGroup/SampleActionGroup.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/actionGroupSchema.xsd"> - <actionGroup name="SampleActionGroup"> - <arguments> - <argument name="person" defaultValue="SamplePerson"/> - </arguments> - <fillField selector="#foo" userInput="{{person.foo}}" stepKey="fillField1"/> - <fillField selector="#bar" userInput="{{person.bar}}" stepKey="fillField2"/> - </actionGroup> - <actionGroup name="ValidateSlideOutPanelField"> - <arguments> - <argument name="property" defaultValue=""/> - </arguments> - <see userInput="{{property.name}}" selector="{{ColumnSection.panelFieldLabel(property.section, property.fieldName, property.section, property.name)}}" stepKey="seePropertyLabel"/> - </actionGroup> -</actionGroups> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Data/SampleData.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Data/SampleData.xml deleted file mode 100644 index 361f06a6ff333..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Data/SampleData.xml +++ /dev/null @@ -1,32 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/DataGenerator/etc/dataProfileSchema.xsd"> - <entity name="SamplePerson" type="samplePerson"> - <data key="foo">foo</data> - <data key="bar">bar</data> - <data key="lastName">Doe</data> - <data key="email" unique="prefix">.email@gmail.com</data> - </entity> - <entity name="OverrideDefaultPerson" type="samplePerson"> - <data key="foo">fizz</data> - <data key="bar">buzz</data> - </entity> - <entity name="AppearanceMinHeightProperty" type="min_height_property"> - <data key="name">Minimum Height</data> - <data key="section">appearance</data> - <data key="fieldName">min_height</data> - </entity> - <entity name="AssertThis" type="samplePerson"> - <data key="firstname">Well</data> - <data key="lastname">Done</data> - <data key="email" unique="prefix">.email@gmail.com</data> - </entity> -</entities> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE.txt deleted file mode 100644 index 49525fd99da9c..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Open Software License ("OSL") v. 3.0 - -This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Open Software License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE_AFL.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE_AFL.txt deleted file mode 100644 index f39d641b18a19..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/LICENSE_AFL.txt +++ /dev/null @@ -1,48 +0,0 @@ - -Academic Free License ("AFL") v. 3.0 - -This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: - -Licensed under the Academic Free License version 3.0 - - 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: - - 1. to reproduce the Original Work in copies, either alone or as part of a collective work; - - 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; - - 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; - - 4. to perform the Original Work publicly; and - - 5. to display the Original Work publicly. - - 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. - - 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. - - 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. - - 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). - - 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. - - 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. - - 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. - - 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). - - 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. - - 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. - - 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. - - 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. - - 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. - - 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Page/SamplePage.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Page/SamplePage.xml deleted file mode 100644 index 6fd2a54c6014e..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Page/SamplePage.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/PageObject.xsd"> - <page name="SamplePage" url="/{{var1}}/{{var2}}.html" area="storefront" module="SampleTests" parameterized="true"> - <section name="SampleSection"/> - </page> -</pages> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/README.md b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/README.md deleted file mode 100644 index f3340b205f1a3..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Magento 2 Functional Tests - -The Functional Tests Module for **Magento_SampleTests** Module. diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Section/SampleSection.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Section/SampleSection.xml deleted file mode 100644 index 6aa54d2fb9b83..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Section/SampleSection.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Page/etc/SectionObject.xsd"> - <section name="SampleSection"> - <element name="oneParamElement" type="button" selector="#element .{{var1}}" parameterized="true"/> - <element name="twoParamElement" type="button" selector="#{{var1}} .{{var2}}" parameterized="true"/> - <element name="threeParamElement" type="button" selector="#{{var1}}-{{var2}} .{{var3}}" parameterized="true"/> - <element name="timeoutElement" type="button" selector="#foo" timeout="30"/> - </section> - <section name="ColumnSection"> - <element name="panelFieldLabel" type="text" selector='//div[@data-index="{{arg1}}"]/descendant::div[@data-index="{{arg2}}"]/label | //div[@data-index="{{arg3}}"]/descendant::*[@class="admin__field-label"]/span[text()="{{arg4}}"]' parameterized="true" /> - </section> -</sections> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml deleted file mode 100644 index 8632c6b600f35..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AdvancedSampleTest.xml +++ /dev/null @@ -1,51 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> - -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="AdvancedSampleTest"> - <annotations> - <title value=""/> - <description value=""/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - <group value="skip"/> - </annotations> - <before> - <createData entity="SamplePerson" stepKey="beforeData"/> - </before> - <after> - - </after> - - <!-- Create an entity that depends on another entity --> - <createData entity="_defaultCategory" stepKey="createCategory"/> - <createData entity="_defaultProduct" stepKey="createProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - - <!-- Parameterized url --> - <amOnPage url="{{SamplePage.url('foo', SamplePerson.bar)}}" stepKey="amOnPage"/> - - <!-- Parameterized selector --> - <grabTextFrom selector="{{SampleSection.twoParamElement(SamplePerson.foo, 'bar')}}" stepKey="grabTextFrom"/> - - <!-- Element with a timeout --> - <click selector="{{SampleSection.timeoutElement}}" stepKey="click"/> - - <!-- ActionGroup --> - <actionGroup ref="SampleActionGroup" stepKey="actionGroup"> - <argument name="person" value="OverrideDefaultPerson"/> - </actionGroup> - - <actionGroup ref="ValidateSlideOutPanelField" stepKey="seeAppearanceMinHeightProperty"> - <argument name="property" value="AppearanceMinHeightProperty"/> - </actionGroup> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml deleted file mode 100644 index 1808424b8a465..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/AssertsTest.xml +++ /dev/null @@ -1,77 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<!-- Test XML Example --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="AssertTest"> - <annotations> - <features value="Test Asserts"/> - <group value="skip"/> - </annotations> - - <createData entity="Simple_US_Customer" stepKey="createData2"/> - <amOnUrl url="https://www.yahoo.com" stepKey="amOnPage"/> - <waitForElementVisible stepKey="wait1" selector="#uh-logo" time="10"/> - <grabTextFrom selector="#uh-logo" stepKey="grabTextFrom1"/> - - <!-- asserts without variable replacement --> - <assertArrayHasKey stepKey="assertArrayHasKey" message="pass"> - <expectedResult type="string">apple</expectedResult> - <actualResult type="const">['orange' => 2, 'apple' => 1]</actualResult> - </assertArrayHasKey> - <assertEquals stepKey="assertEquals1" message="pass"> - <expectedResult type="variable">grabTextFrom1</expectedResult> - <actualResult type="string">Copyright © 2013-2017 Magento, Inc. All rights reserved.</actualResult> - </assertEquals> - <assertFalse stepKey="assertFalse1" message="pass"> - <actualResult type="bool">0</actualResult> - </assertFalse> - <assertGreaterOrEquals stepKey="assertGreaterOrEquals" message="pass"> - <expectedResult type="int">2</expectedResult> - <actualResult type="int">5</actualResult> - </assertGreaterOrEquals> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute1" selector="#username" attribute="class"> - <expectedResult type="string">admin__control-text</expectedResult> - </assertElementContainsAttribute> - - <!-- string type that use created data --> - <assertStringStartsWith stepKey="assert1" message="fail"> - <expectedResult type="string">D</expectedResult> - <actualResult type="string">$$createData1.lastname$$, $$createData1.firstname$$</actualResult> - </assertStringStartsWith> - <assertStringStartsNotWith stepKey="assert2" message="pass"> - <expectedResult type="string">W</expectedResult> - <actualResult type="string">$createData2.firstname$, $createData2.lastname$</actualResult> - </assertStringStartsNotWith> - <assertEquals stepKey="assert5" message="pass"> - <expectedResult type="string">$$createData1.lastname$$</expectedResult> - <actualResult type="string">$$createData1.lastname$$</actualResult> - </assertEquals> - <assertElementContainsAttribute stepKey="assertElementContainsAttribute7" selector="#username" attribute="value"> - <expectedResult type="const">$createData2.firstname$</expectedResult> - </assertElementContainsAttribute> - - <!-- array type that use created data --> - <assertArraySubset stepKey="assert9" message="pass"> - <expectedResult type="array">[$$createData1.lastname$$, $$createData1.firstname$$]</expectedResult> - <actualResult type="array">[$$createData1.lastname$$, $$createData1.firstname$$, 1]</actualResult> - </assertArraySubset> - <assertArraySubset stepKey="assert10" message="pass"> - <expectedResult type="array">[$createData2.firstname$, $createData2.lastname$]</expectedResult> - <actualResult type="array">[$createData2.firstname$, $createData2.lastname$, 1]</actualResult> - </assertArraySubset> - <assertArrayHasKey stepKey="assert3" message="pass"> - <expectedResult type="string">lastname</expectedResult> - <actualResult type="array">['lastname' => $$createData1.lastname$$, 'firstname' => $$createData1.firstname$$]</actualResult> - </assertArrayHasKey> - <assertArrayHasKey stepKey="assert4" message="pass"> - <expectedResult type="string">lastname</expectedResult> - <actualResult type="array">['lastname' => $createData2.lastname$, 'firstname' => $createData2.firstname$]</actualResult> - </assertArrayHasKey> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml deleted file mode 100644 index 73df7dec87abc..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateConfigurableProductByApiTest.xml +++ /dev/null @@ -1,75 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="CreateConfigurableProductByApiTest"> - <annotations> - <features value="Create a Configurable Product By API"/> - <stories value="Create a Configurable Product By API"/> - <group value="skip"/> - </annotations> - <before> - <createData stepKey="categoryHandle" entity="SimpleSubCategory" /> - <createData stepKey="baseConfigProductHandle" entity="BaseConfigurableProduct" > - <requiredEntity createDataKey="categoryHandle"/> - </createData> - <createData stepKey="productAttributeHandle" entity="productAttributeWithDropdownTwoOptions"/> - - <createData stepKey="productAttributeOption1Handle" entity="productAttributeOption1"> - <requiredEntity createDataKey="productAttributeHandle"/> - </createData> - <createData stepKey="productAttributeOption2Handle" entity="productAttributeOption2"> - <requiredEntity createDataKey="productAttributeHandle"/> - </createData> - - <createData stepKey="addToAttributeSetHandle" entity="AddToDefaultSet"> - <requiredEntity createDataKey="productAttributeHandle"/> - </createData> - - <getData stepKey="getAttributeOption1Handle" entity="ProductAttributeOptionGetter" index="1"> - <requiredEntity createDataKey="productAttributeHandle"/> - </getData> - <getData stepKey="getAttributeOption2Handle" entity="ProductAttributeOptionGetter" index="2"> - <requiredEntity createDataKey="productAttributeHandle"/> - </getData> - - <createData stepKey="childProductHandle1" entity="SimpleOne"> - <requiredEntity createDataKey="productAttributeHandle"/> - <requiredEntity createDataKey="getAttributeOption1Handle"/> - </createData> - <createData stepKey="childProductHandle2" entity="SimpleOne"> - <requiredEntity createDataKey="productAttributeHandle"/> - <requiredEntity createDataKey="getAttributeOption2Handle"/> - </createData> - - <createData stepKey="configProductOptionHandle" entity="ConfigurableProductTwoOptions"> - <requiredEntity createDataKey="baseConfigProductHandle"/> - <requiredEntity createDataKey="productAttributeHandle"/> - <requiredEntity createDataKey="getAttributeOption1Handle"/> - <requiredEntity createDataKey="getAttributeOption2Handle"/> - </createData> - - <createData stepKey="configProductHandle1" entity="ConfigurableProductAddChild"> - <requiredEntity createDataKey="childProductHandle1"/> - <requiredEntity createDataKey="baseConfigProductHandle"/> - </createData> - <createData stepKey="configProductHandle2" entity="ConfigurableProductAddChild"> - <requiredEntity createDataKey="childProductHandle2"/> - <requiredEntity createDataKey="baseConfigProductHandle"/> - </createData> - </before> - <after> - <deleteData stepKey="d2" createDataKey="childProductHandle1"/> - <deleteData stepKey="d3" createDataKey="childProductHandle2"/> - <deleteData stepKey="d7" createDataKey="baseConfigProductHandle"/> - <deleteData stepKey="d8" createDataKey="categoryHandle"/> - <deleteData stepKey="d6" createDataKey="productAttributeHandle"/> - </after> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml deleted file mode 100644 index 6106cd7441195..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/CreateSalesRuleByApiTest.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="CreateSalesRuleByApiTest"> - <annotations> - <features value="Create a Sales Rule By API"/> - <stories value="Create a Sales Rule By API"/> - <group value="skip"/> - </annotations> - <before> - <createData stepKey="saleRule" entity="SimpleSalesRule" /> - </before> - <!--see stepKey="test" userInput="$$saleRule.store_labels[0][store_id]$$" selector="test"/--> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/MinimumTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/MinimumTest.xml deleted file mode 100644 index e2e8beacb47be..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/MinimumTest.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<!-- Test XML Example --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="MinimumFieldsTest"> - <annotations> - <title value="Minimum Test"/> - <description value="Minimum Test"/> - <group value="example"/> - </annotations> - <after> - <seeInCurrentUrl url="/admin/admin/" stepKey="seeInCurrentUrl"/> - </after> - <amOnPage url="{{AdminLoginPage.url}}" stepKey="navigateToAdmin"/> - <fillField selector="{{AdminLoginFormSection.username}}" userInput="{{_ENV.MAGENTO_ADMIN_USERNAME}}" stepKey="fillUsername"/> - <fillField selector="{{AdminLoginFormSection.password}}" userInput="{{_ENV.MAGENTO_ADMIN_PASSWORD}}" stepKey="fillPassword"/> - <click selector="{{AdminLoginFormSection.signIn}}" stepKey="clickLogin"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml deleted file mode 100644 index 0d2c1d24c5c04..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/PersistMultipleEntitiesTest.xml +++ /dev/null @@ -1,33 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="PersistMultipleEntitiesTest"> - <annotations> - <group value="skip"/> - </annotations> - <before> - <createData entity="simplesubcategory" stepKey="simplecategory"/> - <createData entity="simpleproduct" stepKey="simpleproduct1"> - <requiredEntity createDataKey="simplecategory"/> - </createData> - <createData entity="simpleproduct" stepKey="simpleproduct2"> - <requiredEntity createDataKey="categoryLink"/> - </createData> - </before> - <after> - <deleteData createDataKey="simpleproduct1" stepKey="deleteProduct1"/> - <deleteData createDataKey="simpleproduct2" stepKey="deleteProduct2"/> - <deleteData createDataKey="simplecategory" stepKey="deleteCategory"/> - </after> - <amOnPage stepKey="s11" url="/$$simplecategory.name$$.html" /> - <waitForPageLoad stepKey="s33"/> - <see stepKey="s35" selector="{{StorefrontCategoryMainSection.productCount}}" userInput="2"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SampleTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SampleTest.xml deleted file mode 100644 index 41299ef814405..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SampleTest.xml +++ /dev/null @@ -1,295 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<!-- Test XML Example --> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="PreAndPostHooksTest"> - <before> - <amOnUrl url="http://127.0.0.1:32772/admin/" stepKey="amOnPage"/> - <createData entity="CustomerEntity1" stepKey="createData1"/> - <createData entity="AssertThis" stepKey="createData2"/> - </before> - <after> - <amOnUrl url="http://127.0.0.1:32772/admin/admin/auth/logout" stepKey="amOnPage"/> - <deleteData createDataKey="createData1" stepKey="deleteData1"/> - <deleteData createDataKey="createData2" stepKey="deleteData2"/> - </after> - </test> - <test name="AllCodeceptionMethodsTest"> - <annotations> - <title value="Create all Codeception methods"/> - <description value="Exercises the Generator to make sure it creates every Codeception method correctly."/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - </annotations> - <acceptPopup stepKey="acceptPopup"/> - <amOnPage url="/admin" stepKey="amOnPage"/> - <amOnSubdomain url="admin" stepKey="amOnSubdomain"/> - <amOnUrl url="http://www.google.com/" stepKey="amOnUrl"/> - <appendField userInput="More Words" selector=".stuff" stepKey="appendField"/> - <attachFile userInput="filename.php" selector="#stuff" stepKey="attachFile"/> - <cancelPopup stepKey="cancelPopup"/> - <checkOption selector="#checkbox" stepKey="checkOption"/> - <clearField selector="#field" stepKey="clearField"/> - <click selector="#button" userInput="Context" stepKey="click1"/> - <click selectorArray="['link' => 'Login']" stepKey="click2"/> - <click selectorArray="['link' => 'Login']" userInput="stuff" stepKey="click3"/> - <clickWithLeftButton selector="#clickHere" stepKey="clickWithLeftButton1" x="23" y="324"/> - <clickWithLeftButton selectorArray="['css' => '.checkout']" stepKey="clickWithLeftButton2" x="23" y="324"/> - <clickWithLeftButton stepKey="clickWithLeftButton3" x="23" y="324"/> - <clickWithRightButton selector="#clickHere" stepKey="clickWithRightButton1" x="23" y="324"/> - <clickWithRightButton selectorArray="['css' => '.checkout']" stepKey="clickWithRightButton2" x="23" y="324"/> - <clickWithRightButton stepKey="clickWithRightButton3" x="23" y="324"/> - <closeTab stepKey="closeTab"/> - <comment userInput="This is a Comment." stepKey="comment"/> - <createData entity="CustomerEntity1" stepKey="createData1"/> - <deleteData createDataKey="createData1" stepKey="deleteData1"/> - <dontSee userInput="Text" stepKey="dontSee1"/> - <dontSee userInput="Text" selector=".title" stepKey="dontSee2"/> - <dontSee userInput="Text" selectorArray="['css' => 'body h1']" stepKey="dontSee3"/> - <dontSeeCheckboxIsChecked selector="#checkbox" stepKey="dontSeeCheckboxIsChecked"/> - <dontSeeCookie userInput="cookieName" stepKey="dontSeeCookie1"/> - <dontSeeCookie userInput="cookieName" parameterArray="['domainName' => 'stuff']" stepKey="dontSeeCookie2"/> - <dontSeeCurrentUrlEquals url="/stuff" stepKey="dontSeeCurrentUrlEquals"/> - <dontSeeCurrentUrlMatches regex="~$/users/(\d+)~" stepKey="dontSeeCurrentUrlMatches"/> - <dontSeeElement selector=".error" stepKey="dontSeeElement1"/> - <dontSeeElement selector="input" parameterArray="['name' => 'login']" stepKey="dontSeeElement2"/> - <dontSeeElementInDOM selector="#stuff" stepKey="dontSeeElementInDOM1"/> - <dontSeeElementInDOM selector="#stuff" parameterArray="['name' => 'login']" stepKey="dontSeeElementInDOM2"/> - <dontSeeInCurrentUrl url="/users/" stepKey="dontSeeInCurrentUrl"/> - <dontSeeInField selector=".field" userInput="stuff" stepKey="dontSeeInField1"/> - <dontSeeInField selectorArray="['name' => 'search']" userInput="Comment Here" stepKey="dontSeeInField2"/> - <dontSeeInFormFields selector="form[name=myform]" parameterArray="['input1' => 'non-existent value', 'input2' => 'other non-existent value']" stepKey="dontSeeInFormFields"/> - <dontSeeInPageSource userInput="Stuff in Page Source" stepKey="dontSeeInPageSource"/> - <!--<dontSeeInSource html="<h1></h1>" stepKey="dontSeeInSource"/>--> - <dontSeeInTitle userInput="Title" stepKey="dontSeeInTitle"/> - <dontSeeLink userInput="Logout" stepKey="dontSeeLink1"/> - <dontSeeLink userInput="Checkout" url="/store/cart.php" stepKey="dontSeeLink2"/> - <dontSeeOptionIsSelected selector="#form .stuff" userInput="Option Name" stepKey="dontSeeOptionIsSelected"/> - <doubleClick selector="#click .here" stepKey="doubleClick"/> - <dragAndDrop selector1="#number1" selector2="#number2" stepKey="dragAndDrop"/> - <executeInSelenium function="function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) {$webdriver->get('http://google.com');}" stepKey="executeInSelenium"/> - <executeJS function="return $('#myField').val()" stepKey="executeJS"/> - <fillField selector="#field" userInput="stuff" stepKey="fillField1"/> - <fillField selectorArray="['name' => 'email']" userInput="stuff" stepKey="fillField2"/> - <grabAttributeFrom selector="#target" userInput="title" stepKey="grabAttributeFrom"/> - <grabCookie userInput="cookie" parameterArray="['domain' => 'www.google.com']" stepKey="grabCookie"/> - <grabFromCurrentUrl regex="~$/user/(\d+)/~" stepKey="grabFromCurrentUrl"/> - <grabMultiple selector="a" userInput="href" stepKey="grabMultiple"/> - <grabPageSource stepKey="grabPageSource1"/> - <grabTextFrom selector="h1" stepKey="grabTextFrom1"/> - <grabValueFrom selector=".form" stepKey="grabValueFrom1"/> - <grabValueFrom selectorArray="['name' => 'username']" stepKey="grabValueFrom2"/> - <loadSessionSnapshot userInput="stuff" stepKey="loadSessionSnapshot1"/> - <loadSessionSnapshot userInput="stuff" stepKey="loadSessionSnapshot2"/> - <makeScreenshot userInput="ScreenshotName" stepKey="makeScreenshot"/> - <maximizeWindow stepKey="maximizeWindow"/> - <moveBack stepKey="moveBack"/> - <moveForward stepKey="moveForward"/> - <moveMouseOver selector="#stuff" stepKey="moveMouseOver1"/> - <moveMouseOver selectorArray="['css' => '.checkout']" stepKey="moveMouseOver2"/> - <moveMouseOver x="5" y="5" stepKey="moveMouseOver3"/> - <moveMouseOver selector="#stuff" x="5" y="5" stepKey="moveMouseOver4"/> - <moveMouseOver selectorArray="['css' => '.checkout']" x="5" y="5" stepKey="moveMouseOver5"/> - <openNewTab stepKey="openNewTab"/> - <pauseExecution stepKey="pauseExecution"/> - <performOn selector=".rememberMe" function="function (WebDriver $I) { $I->see('Remember me next time'); $I->seeElement('#LoginForm_rememberMe'); $I->dontSee('Login'); }" stepKey="performOn1"/> - <performOn selector=".rememberMe" function="ActionSequence::build()->see('Warning')->see('Are you sure you want to delete this?')->click('Yes')" stepKey="performOn2"/> - <pressKey selector="#page" userInput="a" stepKey="pressKey1"/> - <pressKey selector="#page" parameterArray="[['ctrl','a'],'new']" stepKey="pressKey2"/> - <pressKey selector="#page" parameterArray="[['shift','111'],'1','x']" stepKey="pressKey3"/> - <pressKey selector="#page" parameterArray="[['ctrl', 'a'], \Facebook\WebDriver\WebDriverKeys::DELETE]" stepKey="pressKey4"/> - <!--pressKey selector="descendant-or-self::*[@id='page']" userInput="u" stepKey="pressKey5"/--> - <reloadPage stepKey="reloadPage"/> - <resetCookie userInput="cookie" stepKey="resetCookie1"/> - <resetCookie userInput="cookie" parameterArray="['domainName' => 'www.google.com']" stepKey="resetCookie2"/> - <resizeWindow width="800" height="600" stepKey="resizeWindow"/> - <saveSessionSnapshot userInput="stuff" stepKey="saveSessionSnapshot"/> - <scrollTo selector="#place" x="20" y="50" stepKey="scrollTo1"/> - <scrollTo selectorArray="['css' => '.checkout']" x="20" y="50" stepKey="scrollTo2"/> - <see userInput="Stuff" stepKey="see1"/> - <see userInput="More Stuff" selector=".stuff" stepKey="see2"/> - <see userInput="More More Stuff" selectorArray="['css' => 'body h1']" stepKey="see3"/> - <seeCheckboxIsChecked selector="#checkbox" stepKey="seeCheckboxIsChecked"/> - <seeCookie userInput="PHPSESSID" stepKey="seeCookie1"/> - <seeCookie userInput="PHPSESSID" parameterArray="['domainName' => 'www.google.com']" stepKey="seeCookie2"/> - <seeCurrentUrlEquals url="/" stepKey="seeCurrentUrlEquals"/> - <seeCurrentUrlMatches regex="~$/users/(\d+)~" stepKey="seeCurrentUrlMatches"/> - <seeElement selector=".error" stepKey="seeElement1"/> - <seeElement selectorArray="['css' => 'form input']" stepKey="seeElement2"/> - <seeElement selector=".error" parameterArray="['name' => 'login']" stepKey="seeElement3"/> - <seeElement selectorArray="['css' => 'form input']" parameterArray="['name' => 'login']" stepKey="seeElement4"/> - <seeElementInDOM selector="//form/input[type=hidden]" stepKey="seeElementInDOM1"/> - <seeElementInDOM selector="//form/input[type=hidden]" parameterArray="['name' => 'form']" stepKey="seeElementInDOM2"/> - <seeInCurrentUrl url="home" stepKey="seeInCurrentUrl1"/> - <seeInCurrentUrl url="/home/" stepKey="seeInCurrentUrl2"/> - <seeInField userInput="Stuff" selector="#field" stepKey="seeInField1"/> - <seeInField userInput="Stuff" selectorArray="['name' => 'search']" stepKey="seeInField2"/> - <seeInFormFields selector="form[name=myform]" parameterArray="['input1' => 'value','input2' => 'other value']" stepKey="seeInFormFields1"/> - <seeInFormFields selector=".form-class" parameterArray="[['multiselect' => ['value1','value2'],'checkbox[]]' => ['a checked value','another checked value',]]" stepKey="seeInFormFields2"/> - <!--<seeInPageSource html="<h1></h1>" stepKey="seeInPageSource"/>--> - <seeInPopup userInput="Yes in Popup" stepKey="seeInPopup"/> - <!--<seeInSource html="<h1></h1>" stepKey="seeInSource"/>--> - <seeInTitle userInput="In Title" stepKey="seeInTitle"/> - <seeLink userInput="Logout" stepKey="seeLink1"/> - <seeLink userInput="Logout" url="/logout" stepKey="seeLink2"/> - <seeNumberOfElements selector="tr" userInput="10" stepKey="seeNumberOfElements1"/> - <seeNumberOfElements selector="tr" userInput="[0, 10]" stepKey="seeNumberOfElements2"/> - <seeOptionIsSelected selector=".option" userInput="Visa" stepKey="seeOptionIsSelected"/> - <selectOption selector=".dropDown" userInput="Option Name" stepKey="selectOption1"/> - <selectOption selector="//form/select[@name=account]" parameterArray="['Windows','Linux']" stepKey="selectOption2"/> - <selectOption selector="Which OS do you use?" parameterArray="['text' => 'Windows']" stepKey="selectOption3"/> - <setCookie userInput="PHPSESSID" value="stuff" stepKey="setCookie1"/> - <setCookie userInput="PHPSESSID" value="stuff" parameterArray="['domainName' => 'www.google.com']" stepKey="setCookie2"/> - <submitForm selector="#my-form" parameterArray="['field' => ['value','another value',]]" button="#submit" stepKey="submitForm2"/> - <switchToIFrame stepKey="switchToIFrame1"/> - <switchToIFrame userInput="another_frame" stepKey="switchToIFrame2"/> - <switchToNextTab stepKey="switchToNextTab1"/> - <switchToNextTab userInput="2" stepKey="switchToNextTab2"/> - <switchToPreviousTab stepKey="switchToPreviewTab1"/> - <switchToPreviousTab userInput="1" stepKey="switchToPreviewTab2"/> - <switchToWindow stepKey="switchToWindow1"/> - <switchToWindow userInput="another_window" stepKey="switchToWindow2"/> - <typeInPopup userInput="Stuff for popup" stepKey="typeInPopup"/> - <uncheckOption selector="#option" stepKey="uncheckOption"/> - <unselectOption selector="#dropDown" userInput="Option" stepKey="unselectOption"/> - <wait time="15" stepKey="wait"/> - <waitForElement selector="#button" time="10" stepKey="waitForElement"/> - <waitForElementChange selector="#menu" function="function(\WebDriverElement $el) {return $el->isDisplayed();}" time="100" stepKey="waitForElementChange"/> - <waitForElementNotVisible selector="#a_thing .className" time="30" stepKey="waitForElementNotVisible"/> - <waitForElementVisible selector="#a_thing .className" time="15" stepKey="waitForElementVisible"/> - <waitForJS function="return $.active == 0;" time="30" stepKey="waitForJS"/> - <waitForText userInput="foo" time="30" stepKey="waitForText1"/> - <waitForText userInput="foo" selector=".title" time="30" stepKey="waitForText2"/> - </test> - <test name="AllCustomMethodsTest"> - <annotations> - <title value="Create all Custom methods"/> - <description value="Exercises the Generator to make sure it creates every Custom method correctly."/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - </annotations> - <assertElementContainsAttribute selector="#username" attribute="class" expectedValue="admin__control-text" stepKey="assertElementContainsAttribute1"/> - <assertElementContainsAttribute selector="#username" attribute="type" expectedValue="text" stepKey="assertElementContainsAttribute2"/> - <assertElementContainsAttribute selector="#username" attribute="name" expectedValue="login[username]" stepKey="assertElementContainsAttribute3"/> - <assertElementContainsAttribute selector="#username" attribute="autofocus" expectedValue="" stepKey="assertElementContainsAttribute4"/> - <assertElementContainsAttribute selector="#username" attribute="data-validate" expectedValue="{required:true}" stepKey="assertElementContainsAttribute5"/> - <assertElementContainsAttribute selector="#username" attribute="placeholder" expectedValue="user name" stepKey="assertElementContainsAttribute6"/> - <assertElementContainsAttribute selector="#username" attribute="autocomplete" expectedValue="off" stepKey="assertElementContainsAttribute7"/> - <assertElementContainsAttribute selector=".admin__menu-overlay" attribute="style" expectedValue="display: none;" stepKey="assertElementContainsAttribute8"/> - <assertElementContainsAttribute selector=".admin__menu-overlay" attribute="border" expectedValue="0" stepKey="assertElementContainsAttribute9"/> - <closeAdminNotification stepKey="closeAdminNotification1"/> - <searchAndMultiSelectOption selector="#stuff" parameterArray="['Item 1', 'Item 2']" stepKey="searchAndMultiSelect1"/> - <searchAndMultiSelectOption selector="#stuff" parameterArray="['Item 1', 'Item 2']" requiredAction="true" stepKey="searchAndMultiSelect2"/> - <waitForPageLoad stepKey="waitForPageLoad1"/> - <waitForPageLoad time="15" stepKey="waitForPageLoad2"/> - <waitForAjaxLoad stepKey="waitForAjax1"/> - <waitForAjaxLoad time="15" stepKey="waitForAjax2"/> - <dontSeeJsError stepKey="dontSeeJsError"/> - <formatMoney userInput="$300,000" stepKey="formatMoney1"/> - <formatMoney userInput="$300,000" locale="en_US.UTF-8" stepKey="formatMoney2"/> - <mSetLocale userInput="300" locale="en_US.UTF-8" stepKey="mSetLocale1"/> - <mResetLocale stepKey="mResetLocale1"/> - <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear1"/> - <scrollToTopOfPage stepKey="scrollToTopOfPage"/> - <parseFloat userInput="300,000.2325" stepKey="parseFloat1"/> - <magentoCLI stepKey="enableMaintenance1" command="maintenance:enable"/> - </test> - <test name="AllVariableMethodsTest"> - <annotations> - <title value="Create all Methods that support Variables."/> - <description value="Exercises the Generator to make sure it creates every Method that supports a Variable."/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - </annotations> - <grabFromCurrentUrl stepKey="grabFromCurrentUrl1"/> - <amOnPage url="{$randomStuff}" stepKey="amOnPage1"/> - <amOnSubdomain url="{$randomStuff}" stepKey="amOnSubdomain1"/> - <amOnUrl url="{$randomStuff}" stepKey="amOnUrl1"/> - <appendField userInput="{$randomStuff}" selector="#randomField" stepKey="appendField1"/> - <attachFile userInput="{$randomStuff}" selector="#filePathField" stepKey="attachFile1"/> - <click userInput="{$randomStuff}" stepKey="click1"/> - <dontSee userInput="{$randomStuff}" stepKey="dontSee1"/> - <dontSeeCookie userInput="{$randomStuff}" stepKey="dontSeeCookie1"/> - <dontSeeCurrentUrlEquals url="{$randomStuff}" stepKey="dontSeeCurrentUrlEquals1"/> - <dontSeeCurrentUrlMatches regex="{$randomStuff}" stepKey="dontSeeCurrentUrlMatches1"/> - <dontSeeInCurrentUrl url="{$randomStuff}" stepKey="dontSeeInCurrentUrl1"/> - <dontSeeInField selector="#stuff" userInput="{$randomStuff}" stepKey="dontSeeInField1"/> - <dontSeeInPageSource userInput="{$randomStuff}" stepKey="dontSeeInPageSource1"/> - <dontSeeInTitle userInput="{$randomStuff}" stepKey="dontSeeInTitle1"/> - <dontSeeLink userInput="{$randomStuff}" stepKey="dontSeeLink1"/> - <dontSeeOptionIsSelected selector="#dropdown" userInput="{$randomStuff}" stepKey="dontSeeOptionIsSelected1"/> - <fillField userInput="{$randomStuff}" selector="#field" stepKey="fillField1"/> - <grabAttributeFrom selector="#stuff" userInput="{$randomStuff}" stepKey="grabAttributeFrom1"/> - <grabCookie userInput="{$randomStuff}" stepKey="grabValueFrom1"/> - <grabFromCurrentUrl regex="{$randomStuff}" stepKey="grabFromCurrentUrl"/> - <grabMultiple selector="a" userInput="{$randomStuff}" stepKey="grabMultiple1"/> - <loadSessionSnapshot userInput="{$randomStuff}" stepKey="loadSessionSnapshot"/> - <pressKey selector="a" userInput="{$randomStuff}" stepKey="pressKey1"/> - <saveSessionSnapshot userInput="{$randomStuff}" stepKey="saveSessionSnapshot1"/> - <see userInput="{$randomStuff}" stepKey="see1"/> - <seeCookie userInput="{$randomStuff}" stepKey="seeCookie1"/> - <seeCurrentUrlEquals url="{$randomStuff}" stepKey="seeCurrentUrlEquals1"/> - <seeCurrentUrlMatches regex="{$randomStuff}" stepKey="seeCurrentUrlMatches1"/> - <seeInCurrentUrl url="{$randomStuff}" stepKey="seeInCurrentUrl1"/> - <seeInField selector="a" userInput="{$randomStuff}" stepKey="seeInField1"/> - <seeInPopup userInput="{$randomStuff}" stepKey="seeInPopup"/> - <seeInTitle userInput="{$randomStuff}" stepKey="seeInTitle1"/> - <seeLink userInput="{$randomStuff}" stepKey="seeLink1"/> - <seeNumberOfElements selector="#stuff" userInput="{$randomStuff}" stepKey="seeNumberOfElements1"/> - <seeOptionIsSelected selector="#stuff" userInput="{$randomStuff}" stepKey="seeOptionIsSelected1"/> - <selectOption selector="#stuff" userInput="{$randomStuff}" stepKey="selectOption1"/> - <switchToIFrame userInput="{$randomStuff}" stepKey="switchToIFrame1"/> - <switchToNextTab userInput="{$randomStuff}" stepKey="switchToNextTab2"/> - <switchToPreviousTab userInput="{$randomStuff}" stepKey="switchToPreviousTab1"/> - <switchToNextTab userInput="{$randomStuff}" stepKey="switchToNextTab3"/> - <switchToWindow userInput="{$randomStuff}" stepKey="switchToWindow1"/> - <typeInPopup userInput="{$randomStuff}" stepKey="typeInPopup"/> - <unselectOption selector="#option" userInput="{$randomStuff}" stepKey="unselectOption1"/> - <waitForText userInput="{$randomStuff}" time="5" stepKey="waitForText1"/> - </test> - <test name="AllReplacementTest"> - <annotations> - <title value="Exercise reference replacement."/> - <description value="Exercises {{foo}}, $foo$, and $$foo$$ replacement."/> - <severity value="CRITICAL"/> - <testCaseId value="#"/> - </annotations> - - <createData entity="CustomerEntity1" stepKey="testScopeData"/> - <createData entity="AssertThis" stepKey="testScopeData2"/> - - <!-- parameterized url that uses literal params --> - <amOnPage url="{{SamplePage.url('success','success2')}}" stepKey="a0"/> - - <!-- url referencing data that was created in this <test> --> - <amOnPage url="$testScopeData.firstname$.html" stepKey="a1"/> - - <!-- url referencing data that was created in a <before> --> - <amOnPage url="$$createData1.firstname$$.html" stepKey="a2"/> - - <!-- parameterized url that uses created data params --> - <amOnPage url="{{SamplePage.url($testScopeData.firstname$,$testScopeData.lastname$)}}" stepKey="a3"/> - <amOnPage url="{{SamplePage.url($$createData1.firstname$$,$$createData1.lastname$$)}}" stepKey="a4"/> - - <!-- parameterized selector that uses literal params --> - <click selector="{{SampleSection.oneParamElement('success')}}" stepKey="c1"/> - <click selector="{{SampleSection.twoParamElement('success','success2')}}" stepKey="c2"/> - - <!-- parameterized selector with literal, static data, and created data --> - <click selector="{{SampleSection.threeParamElement('John', SamplePerson.lastname, $testScopeData.lastname$)}}" stepKey="c3"/> - - <!-- selector that uses created data --> - <click selector="#$testScopeData.firstname$ .$testScopeData.lastname$" stepKey="c4"/> - <click selector="#$$createData1.firstname$$ .$$createData1.lastname$$" stepKey="c5"/> - - <!-- userInput that uses created data --> - <fillField selector="#sample" userInput="Hello $testScopeData.firstname$ $testScopeData.lastname$" stepKey="f1"/> - <fillField selector="#sample" userInput="Hello $$createData1.firstname$$ $$createData1.lastname$$" stepKey="f2"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml deleted file mode 100644 index 1dcc8d2a128cd..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/SetPaymentConfigurationTest.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="SetPaypalConfigurationTest"> - <annotations> - <group value="skip"/> - </annotations> - <createData entity="SamplePaypalConfig" stepKey="createSamplePaypalConfig"/> - <createData entity="DefaultPayPalConfig" stepKey="restoreDefaultPaypalConfig"/> - </test> - <test name="SetBraintreeConfigurationTest"> - <annotations> - <group value="skip"/> - </annotations> - <createData entity="SampleBraintreeConfig" stepKey="createSampleBraintreeConfig"/> - <createData entity="DefaultBraintreeConfig" stepKey="restoreDefaultBraintreeConfig"/> - </test> -</tests> diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml deleted file mode 100644 index 3dc313f526e90..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/Test/UpdateSimpleProductByApiTest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/magento2-functional-testing-framework/src/Magento/FunctionalTestingFramework/Test/etc/testSchema.xsd"> - <test name="UpdateSimpleProductByApiTest"> - <annotations> - <features value="Update simple product by api test."/> - <stories value="Update simple product by api test."/> - <group value="skip"/> - </annotations> - <before> - <createData stepKey="categoryHandle" entity="SimpleSubCategory"/> - <createData stepKey="productHandle" entity="SimpleProduct" > - <requiredEntity createDataKey="categoryHandle"/> - </createData> - <updateData stepKey="updateProduct" entity="NewSimpleProduct" createDataKey="productHandle"/> - </before> - <after> - <deleteData stepKey="delete" createDataKey="updateProduct"/> - </after> - </test> -</tests> \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/composer.json b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/composer.json deleted file mode 100644 index 93279e77b36a1..0000000000000 --- a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/SampleTests/composer.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "magento/magento2-functional-test-module-sample-tests", - "description": "Magento 2 Functional Test Module Sample Tests", - "type": "magento2-test-module", - "version": "100.0.0-dev", - "license": [ - "OSL-3.0", - "AFL-3.0" - ], - "config": { - "sort-packages": true - }, - "require": { - "magento/magento2-functional-testing-framework": "1.0.0", - "php": "~7.0.13|~7.1.0" - }, - "autoload": { - "psr-4": { - "Magento\\FunctionalTest\\SampleTests\\": "" - } - }, - "extra": { - "map": [ - [ - "*", - "tests/functional/Magento/FunctionalTest/SampleTests" - ] - ] - } -} diff --git a/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/placeholder.txt b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/placeholder.txt new file mode 100644 index 0000000000000..f6e0339af6f3e --- /dev/null +++ b/dev/tests/acceptance/tests/functional/Magento/FunctionalTest/placeholder.txt @@ -0,0 +1 @@ +Placeholder \ No newline at end of file diff --git a/dev/tests/acceptance/tests/functional/_bootstrap.php b/dev/tests/acceptance/tests/functional/_bootstrap.php deleted file mode 100644 index ac7a13ea41d29..0000000000000 --- a/dev/tests/acceptance/tests/functional/_bootstrap.php +++ /dev/null @@ -1,8 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -// Here you can initialize variables that will be available to your tests -require_once dirname(__DIR__) . '/_bootstrap.php'; diff --git a/dev/tests/acceptance/utils/command.php b/dev/tests/acceptance/utils/command.php deleted file mode 100644 index 943e2e9776af4..0000000000000 --- a/dev/tests/acceptance/utils/command.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -if (isset($_POST['command'])) { - $command = urldecode($_POST['command']); - $php = PHP_BINARY ?: (PHP_BINDIR ? PHP_BINDIR . '/php' : 'php'); - $valid = validateCommand($command); - if ($valid) { - exec(escapeCommand($php . ' -f ../../../../bin/magento ' . $command) . " 2>&1", $output, $exitCode); - if ($exitCode == 0) { - http_response_code(202); - } else { - http_response_code(500); - } - echo implode("\n", $output); - } else { - http_response_code(403); - echo "Given command not found valid in Magento CLI Command list."; - } -} else { - http_response_code(412); - echo("Command parameter is not set."); -} - -/** - * Returns escaped command. - * - * @param string $command - * @return string - */ -function escapeCommand($command) -{ - $escapeExceptions = [ - '> /dev/null &' => '--dev-null-amp--' - ]; - - $command = escapeshellcmd( - str_replace(array_keys($escapeExceptions), array_values($escapeExceptions), $command) - ); - - return str_replace(array_values($escapeExceptions), array_keys($escapeExceptions), $command); -} - -/** - * Checks magento list of CLI commands for given $command. Does not check command parameters, just base command. - * @param string $command - * @return bool - */ -function validateCommand($command) -{ - $php = PHP_BINARY ?: (PHP_BINDIR ? PHP_BINDIR . '/php' : 'php'); - exec($php . ' -f ../../../../bin/magento list', $commandList); - // Trim list of commands after first whitespace - $commandList = array_map("trimAfterWhitespace", $commandList); - return in_array(trimAfterWhitespace($command), $commandList); -} - -/** - * Returns given string trimmed of everything after the first found whitespace. - * @param string $string - * @return string - */ -function trimAfterWhitespace($string) -{ - return strtok($string, ' '); -} diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php index 6c1e351ccd1ee..e39d235998326 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductAttributeOptionManagementInterfaceTest.php @@ -111,6 +111,9 @@ public function addDataProvider() 'option_with_value_node_that_starts_with_a_number' => [ array_merge($optionPayload, [AttributeOptionInterface::VALUE => '123_some_text']) ], + 'option_with_value_node_that_is_a_number' => [ + array_merge($optionPayload, [AttributeOptionInterface::VALUE => '123']) + ], ]; } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php index 8edb3ab333891..880af262125d0 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -181,6 +181,62 @@ private function loadWebsiteByCode(string $websiteCode): Website return $website; } + /** + * Test for check that 2 same product create and url_key save. + * + * @return void + */ + public function testSaveTwoSameProduct() + { + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => self::SERVICE_NAME, + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => self::SERVICE_NAME . 'Save', + ], + ]; + + $product1 = [ + 'product' => [ + 'attribute_set_id' => 4, + 'name' => "Test API 1", + 'price' => 254.13, + 'sku' => '1234', + ] + ]; + $product2 = [ + 'product' => [ + 'attribute_set_id' => 4, + 'name' => "Test API 1", + 'price' => 254.13, + 'sku' => '1235', + ] + ]; + + $product1 = $this->_webApiCall($serviceInfo, $product1); + $response = $this->_webApiCall($serviceInfo, $product2); + + $index = null; + foreach ($response['custom_attributes'] as $key => $customAttribute) { + if ($customAttribute['attribute_code'] == 'url_key') { + $index = $key; + break; + } + } + + $this->assertArrayHasKey(ProductInterface::SKU, $response); + + $expectedResult = $product1['custom_attributes'][$index]['value'] . '-1'; + $this->assertEquals($expectedResult, $response['custom_attributes'][$index]['value']); + + $this->deleteProduct('1234'); + $this->deleteProduct('1235'); + } + /** * Test removing association between product and website 1 * @magentoApiDataFixture Magento/Catalog/_files/product_with_two_websites.php @@ -879,6 +935,46 @@ public function testGetList() $this->assertEquals($expectedResult, $response['items'][0]['custom_attributes'][$index]['value']); } + /** + * @return void + * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testGetListWithAdditionalParams() + { + $this->_markTestAsRestOnly(); + $searchCriteria = [ + 'searchCriteria' => [ + 'current_page' => 1, + 'page_size' => 2, + ], + ]; + $additionalParams = urlencode('items[id,custom_attributes[description]]'); + + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => self::RESOURCE_PATH . '?' . http_build_query($searchCriteria) . '&fields=' . + $additionalParams, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_GET, + ], + ]; + + $response = $this->_webApiCall($serviceInfo, $searchCriteria); + + $this->assertArrayHasKey('items', $response); + $this->assertTrue(count($response['items']) > 0); + + $indexDescription = null; + foreach ($response['items'][0]['custom_attributes'] as $key => $customAttribute) { + if ($customAttribute['attribute_code'] == 'description') { + $indexDescription = $key; + } + } + + $this->assertNotNull($response['items'][0]['custom_attributes'][$indexDescription]['attribute_code']); + $this->assertNotNull($response['items'][0]['custom_attributes'][$indexDescription]['value']); + $this->assertCount(1, $response['items'][0]['custom_attributes']); + } + /** * @magentoApiDataFixture Magento/Catalog/_files/products_with_websites_and_stores.php * @return void diff --git a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/GuestItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/GuestItemRepositoryTest.php index e63bbc35b1c1b..1664b5f45349b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/GuestItemRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/GuestItemRepositoryTest.php @@ -119,7 +119,6 @@ public function testSave() ], ]; $this->assertTrue($this->_webApiCall($serviceInfo, $requestData)); -// $quote->load('test_order_item_with_message', 'reserved_order_id'); $messageId = $quote->getItemByProduct($product)->getGiftMessageId(); /** @var \Magento\GiftMessage\Model\Message $message */ $message = $this->objectManager->create(\Magento\GiftMessage\Model\Message::class)->load($messageId); diff --git a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/ItemRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/ItemRepositoryTest.php index c13619ec75fea..38a633a594f62 100644 --- a/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/ItemRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GiftMessage/Api/ItemRepositoryTest.php @@ -145,7 +145,6 @@ public function testSave() ], ]; $this->assertTrue($this->_webApiCall($serviceInfo, $requestData)); -// $quote->load('test_order_item_with_message', 'reserved_order_id'); $messageId = $quote->getItemByProduct($product)->getGiftMessageId(); /** @var \Magento\GiftMessage\Model\Message $message */ $message = $this->objectManager->create(\Magento\GiftMessage\Model\Message::class)->load($messageId); @@ -193,7 +192,6 @@ public function testSaveForMyCart() ], ]; $this->assertTrue($this->_webApiCall($serviceInfo, $requestData)); -// $quote->load('test_order_item_with_message', 'reserved_order_id'); $messageId = $quote->getItemByProduct($product)->getGiftMessageId(); /** @var \Magento\GiftMessage\Model\Message $message */ $message = $this->objectManager->create(\Magento\GiftMessage\Model\Message::class)->load($messageId); diff --git a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartTotalRepositoryTest.php b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartTotalRepositoryTest.php index 609ae1cfe094c..a001cae645434 100644 --- a/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartTotalRepositoryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Quote/Api/CartTotalRepositoryTest.php @@ -162,22 +162,22 @@ protected function getQuoteItemTotalsData(\Magento\Quote\Model\Quote $quote) $item = array_shift($items); return [ ItemTotals::KEY_ITEM_ID => $item->getItemId(), - ItemTotals::KEY_PRICE => intval($item->getPrice()), - ItemTotals::KEY_BASE_PRICE => intval($item->getBasePrice()), + ItemTotals::KEY_PRICE => (int)$item->getPrice(), + ItemTotals::KEY_BASE_PRICE => (int)$item->getBasePrice(), ItemTotals::KEY_QTY => $item->getQty(), - ItemTotals::KEY_ROW_TOTAL => intval($item->getRowTotal()), - ItemTotals::KEY_BASE_ROW_TOTAL => intval($item->getBaseRowTotal()), - ItemTotals::KEY_ROW_TOTAL_WITH_DISCOUNT => intval($item->getRowTotalWithDiscount()), - ItemTotals::KEY_TAX_AMOUNT => intval($item->getTaxAmount()), - ItemTotals::KEY_BASE_TAX_AMOUNT => intval($item->getBaseTaxAmount()), - ItemTotals::KEY_TAX_PERCENT => intval($item->getTaxPercent()), - ItemTotals::KEY_DISCOUNT_AMOUNT => intval($item->getDiscountAmount()), - ItemTotals::KEY_BASE_DISCOUNT_AMOUNT => intval($item->getBaseDiscountAmount()), - ItemTotals::KEY_DISCOUNT_PERCENT => intval($item->getDiscountPercent()), - ItemTotals::KEY_PRICE_INCL_TAX => intval($item->getPriceInclTax()), - ItemTotals::KEY_BASE_PRICE_INCL_TAX => intval($item->getBasePriceInclTax()), - ItemTotals::KEY_ROW_TOTAL_INCL_TAX => intval($item->getRowTotalInclTax()), - ItemTotals::KEY_BASE_ROW_TOTAL_INCL_TAX => intval($item->getBaseRowTotalInclTax()), + ItemTotals::KEY_ROW_TOTAL => (int)$item->getRowTotal(), + ItemTotals::KEY_BASE_ROW_TOTAL => (int)$item->getBaseRowTotal(), + ItemTotals::KEY_ROW_TOTAL_WITH_DISCOUNT => (int)$item->getRowTotalWithDiscount(), + ItemTotals::KEY_TAX_AMOUNT => (int)$item->getTaxAmount(), + ItemTotals::KEY_BASE_TAX_AMOUNT => (int)$item->getBaseTaxAmount(), + ItemTotals::KEY_TAX_PERCENT => (int)$item->getTaxPercent(), + ItemTotals::KEY_DISCOUNT_AMOUNT => (int)$item->getDiscountAmount(), + ItemTotals::KEY_BASE_DISCOUNT_AMOUNT => (int)$item->getBaseDiscountAmount(), + ItemTotals::KEY_DISCOUNT_PERCENT => (int)$item->getDiscountPercent(), + ItemTotals::KEY_PRICE_INCL_TAX => (int)$item->getPriceInclTax(), + ItemTotals::KEY_BASE_PRICE_INCL_TAX => (int)$item->getBasePriceInclTax(), + ItemTotals::KEY_ROW_TOTAL_INCL_TAX => (int)$item->getRowTotalInclTax(), + ItemTotals::KEY_BASE_ROW_TOTAL_INCL_TAX => (int)$item->getBaseRowTotalInclTax(), ItemTotals::KEY_WEEE_TAX_APPLIED_AMOUNT => $item->getWeeeTaxAppliedAmount(), ItemTotals::KEY_WEEE_TAX_APPLIED => $item->getWeeeTaxApplied(), ItemTotals::KEY_NAME => $item->getName(), diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php index 0135963d3f989..a8c18bd86e189 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/CreditmemoCreateTest.php @@ -40,7 +40,6 @@ public function testInvoke() $orderCollection = $this->objectManager->get(\Magento\Sales\Model\ResourceModel\Order\Collection::class); $order = $orderCollection->getFirstItem(); -// $order = $this->objectManager->create('Magento\Sales\Model\Order')->loadByIncrementId('100000001'); /** @var \Magento\Sales\Model\Order\Item $orderItem */ $orderItem = current($order->getAllItems()); $items = [ diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentLabelGetTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentLabelGetTest.php index 1a957e51b1efa..b496f08cc3e36 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentLabelGetTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentLabelGetTest.php @@ -50,6 +50,6 @@ public function testShipmentGet() ], ]; $result = $this->_webApiCall($serviceInfo, ['id' => $shipment->getId()]); - $this->assertEquals($result, 'test_shipping_label'); + $this->assertEquals($result, base64_encode('test_shipping_label')); } } diff --git a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentListTest.php b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentListTest.php index ad776da3ae439..1f5c7415ace3d 100644 --- a/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentListTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Sales/Service/V1/ShipmentListTest.php @@ -91,5 +91,7 @@ public function testShipmentList() $this->assertEquals($searchData, $result['search_criteria']); $this->assertEquals('100000002', $result['items'][0]['increment_id']); $this->assertEquals('100000003', $result['items'][1]['increment_id']); + $this->assertEquals(base64_encode('shipping_label_100000002'), $result['items'][0]['shipping_label']); + $this->assertEquals(base64_encode('shipping_label_100000003'), $result['items'][1]['shipping_label']); } } diff --git a/dev/tests/functional/.gitignore b/dev/tests/functional/.gitignore index d47f62b56bb9a..92e3004580b0c 100644 --- a/dev/tests/functional/.gitignore +++ b/dev/tests/functional/.gitignore @@ -6,3 +6,4 @@ /vendor phpunit.xml credentials.xml +.htaccess diff --git a/dev/tests/functional/bootstrap.php b/dev/tests/functional/bootstrap.php index 3c0fd4cc388cc..a0a7b62a5bba8 100644 --- a/dev/tests/functional/bootstrap.php +++ b/dev/tests/functional/bootstrap.php @@ -6,23 +6,25 @@ defined('MTF_BOOT_FILE') || define('MTF_BOOT_FILE', __FILE__); defined('MTF_BP') || define('MTF_BP', str_replace('\\', '/', (__DIR__))); +defined('BP') || define('BP', str_replace('\\', '/', dirname(dirname(dirname((__DIR__)))))); defined('MTF_TESTS_PATH') || define('MTF_TESTS_PATH', MTF_BP . '/tests/app/'); defined('MTF_STATES_PATH') || define('MTF_STATES_PATH', MTF_BP . '/lib/Magento/Mtf/App/State/'); -require_once __DIR__ . '/../../../app/bootstrap.php'; restore_error_handler(); -$vendorAutoload = __DIR__ . '/vendor/autoload.php'; - -if (isset($composerAutoloader)) { - /** var $mtfComposerAutoload \Composer\Autoload\ClassLoader */ - $mtfComposerAutoload = include $vendorAutoload; - $composerAutoloader->addClassMap($mtfComposerAutoload->getClassMap()); -} else { - $composerAutoloader = include $vendorAutoload; -} - +include __DIR__ . '/vendor/autoload.php'; setCustomErrorHandler(); +/* Custom umask value may be provided in optional mage_umask file in root */ +$umaskFile = BP . '/magento_umask'; +$mask = file_exists($umaskFile) ? octdec(file_get_contents($umaskFile)) : 002; +umask($mask); + +date_default_timezone_set('UTC'); + +/* For data consistency between displaying (printing) and serialization a float number */ +ini_set('precision', 14); +ini_set('serialize_precision', 14); + /** * Set custom error handler */ diff --git a/dev/tests/functional/lib/Magento/Mtf/Constraint/AbstractAssertForm.php b/dev/tests/functional/lib/Magento/Mtf/Constraint/AbstractAssertForm.php index eb04450d5261d..3651c41ea8918 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Constraint/AbstractAssertForm.php +++ b/dev/tests/functional/lib/Magento/Mtf/Constraint/AbstractAssertForm.php @@ -53,7 +53,7 @@ protected function verifyData(array $fixtureData, array $formData, $isStrict = f } $formValue = isset($formData[$key]) ? $formData[$key] : null; if (is_numeric($formValue)) { - $formValue = floatval($formValue); + $formValue = (float)$formValue; } if (null === $formValue) { diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Factory.php b/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Factory.php index 5bb1efee62959..192cab5751986 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Factory.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Factory.php @@ -6,9 +6,6 @@ namespace Magento\Mtf\Util\Generate; -use Magento\Framework\App; -use Magento\Framework\ObjectManagerInterface; - /** * Factory classes generator. * @@ -16,24 +13,10 @@ */ class Factory extends AbstractGenerate { - /** - * @var \Magento\Framework\ObjectManagerInterface - */ - protected $objectManager; - - /** - * @constructor - * @param ObjectManagerInterface $objectManager - */ - public function __construct(ObjectManagerInterface $objectManager) - { - $this->objectManager = $objectManager; - } - /** * Generate Handlers. * - * @return \Magento\Framework\App\ResponseInterface + * @return bool */ public function launch() { @@ -43,7 +26,7 @@ public function launch() $this->objectManager->create(\Magento\Mtf\Util\Generate\Factory\Page::class)->launch(); $this->objectManager->create(\Magento\Mtf\Util\Generate\Factory\Repository::class)->launch(); - return $this->objectManager->get(\Magento\Framework\App\ResponseInterface::class); + return true; } /** diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Fixture/SchemaXml.php b/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Fixture/SchemaXml.php index aa85299deea44..6d1d5b6f4b349 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Fixture/SchemaXml.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/Generate/Fixture/SchemaXml.php @@ -145,7 +145,7 @@ protected function generateFixtureXml(array $config) foreach ($fields as $fieldName => $fieldValue) { $field = $this->dom->createElement('field'); $field->setAttribute('name', $fieldName); - $field->setAttribute('is_required', intval($fieldValue['is_required'])); + $field->setAttribute('is_required', (int)$fieldValue['is_required']); $fixture->appendChild($field); } diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/ModuleResolver/SequenceSorter.php b/dev/tests/functional/lib/Magento/Mtf/Util/ModuleResolver/SequenceSorter.php index a4b438a1d2de0..110d9d5fbd6c3 100644 --- a/dev/tests/functional/lib/Magento/Mtf/Util/ModuleResolver/SequenceSorter.php +++ b/dev/tests/functional/lib/Magento/Mtf/Util/ModuleResolver/SequenceSorter.php @@ -10,37 +10,6 @@ */ class SequenceSorter implements SequenceSorterInterface { - /** - * Magento ObjectManager. - * - * @var \Magento\Framework\ObjectManagerInterface - */ - protected $magentoObjectManager; - - /** - * @constructor - */ - public function __construct() - { - $this->initObjectManager(); - } - - /** - * Initialize Magento ObjectManager. - * - * @return void - */ - protected function initObjectManager() - { - if (!$this->magentoObjectManager) { - $objectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory( - BP, - $_SERVER - ); - $this->magentoObjectManager = $objectManagerFactory->create($_SERVER); - } - } - /** * Get Magento module sequence load. * @@ -48,7 +17,8 @@ protected function initObjectManager() */ protected function getModuleSequence() { - return $this->magentoObjectManager->create(\Magento\Framework\Module\ModuleList\Loader::class)->load(); + $ds = DIRECTORY_SEPARATOR; + return json_decode(file_get_contents(MTF_BP . $ds . 'generated' . $ds . 'moduleSequence.json'), true); } /** diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/System/Config/AnalyticsForm.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/System/Config/AnalyticsForm.php index 07b62a9518ae4..bf1f55915108b 100644 --- a/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/System/Config/AnalyticsForm.php +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Block/System/Config/AnalyticsForm.php @@ -95,12 +95,17 @@ public function getAnalyticsStatus() /** * @param string $vertical - * @return array|string + * @return $this */ public function setAnalyticsVertical($vertical) { - return $this->_rootElement->find($this->analyticsVertical, Locator::SELECTOR_CSS, 'select') - ->setValue($vertical); + + $element = $this->_rootElement->find($this->analyticsVertical, Locator::SELECTOR_CSS, 'select'); + if ($element->isVisible()) { + $element->setValue($vertical); + } + + return $this; } /** diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsDisabled.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsDisabled.php index 0f65835a32aa7..b061a5d644484 100644 --- a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsDisabled.php +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsDisabled.php @@ -34,6 +34,10 @@ public function processAssert(ConfigAnalytics $configAnalytics, OpenAnalyticsCon 'Subscription status: Disabled', 'Magento Advanced Reporting service subscription status is not disabled.' ); + \PHPUnit_Framework_Assert::assertFalse( + (bool)$configAnalytics->getAnalyticsForm()->getAnalyticsVerticalScope(), + 'Industry Data is visible.' + ); } /** diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsEnabled.php b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsEnabled.php index 8fd04e06b14bb..46d5d8e2d5f78 100644 --- a/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsEnabled.php +++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/Constraint/AssertConfigAnalyticsEnabled.php @@ -29,12 +29,15 @@ public function processAssert(ConfigAnalytics $configAnalytics, OpenAnalyticsCon (bool)$configAnalytics->getAnalyticsForm()->isAnalyticsEnabled(), 'Magento Advanced Reporting service is not enabled.' ); - \PHPUnit_Framework_Assert::assertEquals( $configAnalytics->getAnalyticsForm()->getAnalyticsStatus(), 'Subscription status: Pending', 'Magento Advanced Reporting service subscription status is not pending.' ); + \PHPUnit_Framework_Assert::assertTrue( + (bool)$configAnalytics->getAnalyticsForm()->getAnalyticsVerticalScope(), + 'Industry Data is not visible.' + ); } /** diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpUsedOnFrontend.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpUsedOnFrontend.php index a91597e3b9881..bf755118235cf 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpUsedOnFrontend.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpUsedOnFrontend.php @@ -22,7 +22,7 @@ class AssertHttpUsedOnFrontend extends AbstractConstraint * * @var string */ - private $unsecuredProtocol = \Magento\Framework\HTTP\PhpEnvironment\Request::SCHEME_HTTP; + const SCHEME_HTTP = 'http'; /** * Browser interface. @@ -53,11 +53,11 @@ public function processAssert(BrowserInterface $browser, Customer $customer) // Log in to Customer Account on Storefront to assert that http is used indeed. $this->objectManager->create(LogInCustomerOnStorefront::class, ['customer' => $this->customer])->run(); - $this->assertUsedProtocol($this->unsecuredProtocol); + $this->assertUsedProtocol(self::SCHEME_HTTP); // Log out from Customer Account on Storefront to assert that JS is deployed validly as a part of statics. $this->objectManager->create(LogOutCustomerOnStorefront::class)->run(); - $this->assertUsedProtocol($this->unsecuredProtocol); + $this->assertUsedProtocol(self::SCHEME_HTTP); } /** diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpsUsedOnBackend.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpsUsedOnBackend.php index 4b04eb9690a98..2d592d535ad68 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpsUsedOnBackend.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Constraint/AssertHttpsUsedOnBackend.php @@ -16,18 +16,10 @@ class AssertHttpsUsedOnBackend extends AbstractConstraint { /** - * Secured protocol format. - * - * @var string - */ - private $securedProtocol = \Magento\Framework\HTTP\PhpEnvironment\Request::SCHEME_HTTPS; - - /** - * Unsecured protocol format. - * - * @var string + * Protocols */ - private $unsecuredProtocol = \Magento\Framework\HTTP\PhpEnvironment\Request::SCHEME_HTTP; + const SCHEME_HTTP = 'http'; + const SCHEME_HTTPS = 'https'; /** * Browser interface. @@ -50,7 +42,7 @@ public function processAssert(BrowserInterface $browser, Dashboard $adminDashboa // Open specified Admin page using Navigation Menu to assert that JS is deployed validly as a part of statics. $adminDashboardPage->open()->getMenuBlock()->navigate($navMenuPath); - $this->assertUsedProtocol($this->securedProtocol); + $this->assertUsedProtocol(self::SCHEME_HTTPS); $this->assertDirectHttpUnavailable(); } @@ -80,10 +72,10 @@ protected function assertUsedProtocol($expectedProtocol) */ protected function assertDirectHttpUnavailable() { - $fakeUrl = str_replace($this->securedProtocol, $this->unsecuredProtocol, $this->browser->getUrl()); + $fakeUrl = str_replace(self::SCHEME_HTTPS, self::SCHEME_HTTP, $this->browser->getUrl()); $this->browser->open($fakeUrl); \PHPUnit_Framework_Assert::assertStringStartsWith( - $this->securedProtocol, + self::SCHEME_HTTPS, $this->browser->getUrl(), 'Merchant is not redirected to https if tries to access the Admin panel page directly via http.' ); diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php index 8b0cc6a242b9b..4ba335511c82c 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View.php @@ -96,7 +96,7 @@ class View extends AbstractConfigureBlock * * @var string */ - protected $productDescription = '.product.attribute.description'; + protected $productDescription = '.product.attribute.description .value'; /** * Product short-description element. diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php index 9f05a4ade8a37..ccb0d43337562 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Block/Product/View/CustomOptions.php @@ -231,9 +231,9 @@ protected function getFieldData(SimpleElement $option) return [ 'options' => [ [ - 'price' => floatval($price), - 'max_characters' => $maxCharacters, - ], + 'price' => (float)$price, + 'max_characters' => $maxCharacters + ] ] ]; } @@ -262,11 +262,11 @@ protected function getFileData(SimpleElement $option) return [ 'options' => [ [ - 'price' => floatval($price), + 'price' => (float)$price, 'file_extension' => $this->getOptionNotice($option, 1), 'image_size_x' => preg_replace('/[^0-9]/', '', $this->getOptionNotice($option, 2)), - 'image_size_y' => preg_replace('/[^0-9]/', '', $this->getOptionNotice($option, 3)), - ], + 'image_size_y' => preg_replace('/[^0-9]/', '', $this->getOptionNotice($option, 3)) + ] ] ]; } @@ -344,8 +344,8 @@ protected function getDateData(SimpleElement $option) return [ 'options' => [ [ - 'price' => floatval($price), - ], + 'price' => (float)$price + ] ] ]; } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductDuplicateForm.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductDuplicateForm.php index 5d93496151195..7e5fb8291a884 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductDuplicateForm.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Constraint/AssertProductDuplicateForm.php @@ -116,7 +116,7 @@ function (&$item, $key, $formattingOptions) { protected function prepareUrlKey($urlKey) { preg_match("~\d+$~", $urlKey, $matches); - $key = intval($matches[0]) + 1; + $key = (int)$matches[0] + 1; return str_replace($matches[0], $key, $urlKey); } diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php index 16931f3662c75..376a1d1864e74 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Handler/CatalogProductSimple/Curl.php @@ -411,7 +411,7 @@ protected function prepareQuantityAndStockStatus() : ['is_in_stock' => 'In Stock']; if (!isset($quantityAndStockStatus['is_in_stock'])) { - $qty = isset($quantityAndStockStatus['qty']) ? intval($quantityAndStockStatus['qty']) : null; + $qty = isset($quantityAndStockStatus['qty']) ? (int)$quantityAndStockStatus['qty'] : null; $quantityAndStockStatus['is_in_stock'] = 0 === $qty ? 'Out of Stock' : 'In Stock'; } diff --git a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleForm.php b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleForm.php index e90b0a731e88a..2fd462b76f04c 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleForm.php +++ b/dev/tests/functional/tests/app/Magento/CatalogRule/Test/Constraint/AssertCatalogPriceRuleForm.php @@ -42,10 +42,10 @@ public function processAssert( $fixtureData = $catalogPriceRule->getData(); //convert discount_amount to float to compare if (isset($formData['discount_amount'])) { - $formData['discount_amount'] = floatval($formData['discount_amount']); + $formData['discount_amount'] = (float)$formData['discount_amount']; } if (isset($fixtureData['discount_amount'])) { - $fixtureData['discount_amount'] = floatval($fixtureData['discount_amount']); + $fixtureData['discount_amount'] = (float)$fixtureData['discount_amount']; } $diff = $this->verifyData($formData, $fixtureData); \PHPUnit_Framework_Assert::assertTrue( diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php index 400289eccda15..e2193b799c3be 100644 --- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php +++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php @@ -70,7 +70,7 @@ private function createProducts(FixtureFactory $fixtureFactory, $productsData) $products[] = $product; } elseif ($this->data === null) { - $this->data = strval($productData); + $this->data = (string)$productData; } } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart.php index e7b0df0d81638..d1a6ce2d07f2b 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Cart.php @@ -172,8 +172,17 @@ public function getOnepageLinkBlock() public function braintreePaypalCheckout() { $currentWindow = $this->browser->getCurrentWindow(); + // Button can be enabled/disabled few times. + sleep(2); + + $windowsCount = count($this->browser->getWindowHandles()); $this->_rootElement->find($this->braintreePaypalCheckoutButton, Locator::SELECTOR_XPATH) ->click(); + $browser = $this->browser; + $this->browser->waitUntil(function () use ($browser, $windowsCount) { + return count($browser->getWindowHandles()) === ($windowsCount + 1) ? true: null; + }); + return $currentWindow; } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddProductsToTheCartStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddProductsToTheCartStep.php index 37a4d5c26189f..42d6c4502ef49 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddProductsToTheCartStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddProductsToTheCartStep.php @@ -123,6 +123,7 @@ public function run() } } $cart['data']['items'] = ['products' => $this->products]; + sleep(10); return ['cart' => $this->fixtureFactory->createByCode('cart', $cart)]; } } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/EstimateShippingAndTaxStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/EstimateShippingAndTaxStep.php index 21da4c66fa2f3..0780b7d13a285 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/EstimateShippingAndTaxStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/EstimateShippingAndTaxStep.php @@ -107,6 +107,7 @@ public function run() { $this->checkoutCart->open(); $this->checkoutCart->getCartBlock()->waitCartContainerLoading(); + sleep(20); /** @var \Magento\Checkout\Test\Fixture\Cart $cart */ if ($this->cart !== null) { $cart = $this->fixtureFactory->createByCode( diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php index 9df49d2440568..d951d84bab78d 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/SelectCheckoutMethodStep.php @@ -91,8 +91,10 @@ public function __construct( */ public function run() { + sleep(20); $this->processLogin(); $this->processRegister(); + sleep(20); } /** diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductAttributeOptionInLayeredNavigation.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductAttributeOptionInLayeredNavigation.php index d4af0b0bd1a22..9591c27496ea8 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductAttributeOptionInLayeredNavigation.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductAttributeOptionInLayeredNavigation.php @@ -60,7 +60,7 @@ public function processAssert( $filters = $catalogCategoryView->getLayeredNavigationBlock()->getFilterContents($frontendAttributeLabel); - \PHPUnit\Framework\Assert::assertFalse( + \PHPUnit_Framework_Assert::assertFalse( in_array(strtoupper($outOfStockOption), $filters), 'Out of Stock attribute option is present in layered navigation on category page.' ); diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductDuplicateForm.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductDuplicateForm.php index cb9e068b5727b..185046f099c30 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductDuplicateForm.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductDuplicateForm.php @@ -70,7 +70,7 @@ protected function prepareFixtureData(array $data, array $sortFields = []) protected function prepareUrlKey($urlKey) { preg_match("~\d+$~", $urlKey, $matches); - $key = intval($matches[0]) + 1; + $key = (int)$matches[0] + 1; return str_replace($matches[0], $key, $urlKey); } diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php index 1c697d9f1e5da..f1a8b17ae2855 100644 --- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.php @@ -6,9 +6,6 @@ namespace Magento\Customer\Test\TestCase; -use Magento\Config\Test\Fixture\ConfigData; -use Magento\Customer\Test\Constraint\AssertChangingWebsiteChangeCountries; -use Magento\Framework\App\ObjectManager; use Magento\Mtf\Fixture\FixtureFactory; use Magento\Mtf\Fixture\FixtureInterface; use Magento\Mtf\TestCase\Injectable; diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Constraint/AssertDownloadableDuplicateForm.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Constraint/AssertDownloadableDuplicateForm.php index e5d97e1511e71..2033189214e12 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Constraint/AssertDownloadableDuplicateForm.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Constraint/AssertDownloadableDuplicateForm.php @@ -32,11 +32,7 @@ protected function sortDownloadableArray(array $fields) usort( $fields, function ($row1, $row2) { - if ($row1['sort_order'] == $row2['sort_order']) { - return 0; - } - - return ($row1['sort_order'] < $row2['sort_order']) ? -1 : 1; + return $row1['sort_order'] <=> $row2['sort_order']; } ); diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Webapi.php b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Webapi.php index 434c78e55c69b..d14d6754b12ec 100644 --- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Webapi.php +++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/Handler/DownloadableProduct/Webapi.php @@ -148,7 +148,7 @@ protected function prepareLinkData(array $link) 'title' => $link['title'], 'sort_order' => isset($link['sort_order']) ? $link['sort_order'] : 0, 'is_shareable' => $link['is_shareable'], - 'price' => floatval($link['price']), + 'price' => (float)$link['price'], 'number_of_downloads' => isset($link['number_of_downloads']) ? $link['number_of_downloads'] : 0, 'link_type' => $link['type'], 'link_url' => isset($link['link_url']) ? $link['link_url'] : null, diff --git a/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportIntervalResult.php b/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportIntervalResult.php index c28023f8411cd..ea04dbd31b027 100644 --- a/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportIntervalResult.php +++ b/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportIntervalResult.php @@ -52,7 +52,7 @@ protected function prepareSalesResult($salesResult) { $data = []; foreach ($salesResult as $key => $result) { - $data[$key] = floatval($result); + $data[$key] = (float)$result; } return $data; diff --git a/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportTotalResult.php b/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportTotalResult.php index 137788441b6d6..7c3b95a7d54af 100644 --- a/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportTotalResult.php +++ b/dev/tests/functional/tests/app/Magento/Reports/Test/Constraint/AssertSalesReportTotalResult.php @@ -52,7 +52,7 @@ protected function prepareSalesResult($salesResult) { $data = []; foreach ($salesResult as $key => $result) { - $data[$key] = floatval($result); + $data[$key] = (float)$result; } return $data; diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/Handler/StoreGroup/Curl.php b/dev/tests/functional/tests/app/Magento/Store/Test/Handler/StoreGroup/Curl.php index be74cc92223cb..d593165405102 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/Handler/StoreGroup/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/Handler/StoreGroup/Curl.php @@ -65,7 +65,7 @@ protected function getStoreGroupIdByGroupName($storeName) throw new \Exception('Cannot find store group id'); } - return intval($matches[1]); + return (int)$matches[1]; } /** diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/Handler/Website/Curl.php b/dev/tests/functional/tests/app/Magento/Store/Test/Handler/Website/Curl.php index 95a8ac8d203dd..ecc59ebb55ca5 100644 --- a/dev/tests/functional/tests/app/Magento/Store/Test/Handler/Website/Curl.php +++ b/dev/tests/functional/tests/app/Magento/Store/Test/Handler/Website/Curl.php @@ -119,7 +119,7 @@ protected function getWebSiteIdByWebsiteName($websiteName) throw new \Exception('Cannot find website id.'); } - return intval($matches[1]); + return (int)$matches[1]; } /** diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php index c98b5a874a762..170fef137ec9f 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/FormSections.php @@ -6,7 +6,6 @@ namespace Magento\Ui\Test\Block\Adminhtml; -use Magento\Framework\Exception\LocalizedException; use Magento\Mtf\Fixture\InjectableFixture; /** @@ -53,13 +52,13 @@ protected function openContainer($sectionName) * * @param string $sectionName * @return $this - * @throws LocalizedException if section is not visible + * @throws \Exception if section is not visible */ public function openSection($sectionName) { $container = $this->getContainerElement($sectionName); if (!$container->isVisible()) { - throw new LocalizedException(__('Container is not found "' . $sectionName . '""')); + throw new \Exception('Container is not found "' . $sectionName . '""'); } $section = $container->find($this->collapsedSection); if ($section->isVisible()) { diff --git a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/Fixture/UrlRewrite/TargetPath.php b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/Fixture/UrlRewrite/TargetPath.php index cc2763c0e3cb9..7f0c29da03784 100644 --- a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/Fixture/UrlRewrite/TargetPath.php +++ b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/Fixture/UrlRewrite/TargetPath.php @@ -44,7 +44,7 @@ public function __construct(FixtureFactory $fixtureFactory, array $params, $data $id = $this->entity->hasData('id') ? $this->entity->getId() : $this->entity->getPageId(); $this->data = preg_replace('`(%.*?%)`', $id, $data['entity']); } else { - $this->data = strval($data['entity']); + $this->data = (string)$data['entity']; } } diff --git a/dev/tests/functional/utils/bootstrap.php b/dev/tests/functional/utils/bootstrap.php index 00c548e6b5cef..0b74dc6727a13 100644 --- a/dev/tests/functional/utils/bootstrap.php +++ b/dev/tests/functional/utils/bootstrap.php @@ -16,6 +16,3 @@ $objectManager = \Magento\Mtf\ObjectManagerFactory::getObjectManager(); \Magento\Mtf\ObjectManagerFactory::configure($objectManager); - -$magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); -$magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); diff --git a/dev/tests/functional/utils/generate.php b/dev/tests/functional/utils/generate.php index e9a81a1eea224..61bcc3523f551 100644 --- a/dev/tests/functional/utils/generate.php +++ b/dev/tests/functional/utils/generate.php @@ -3,19 +3,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\Framework\Filesystem; require_once dirname(__FILE__) . '/' . 'bootstrap.php'; -// Generate fixtures -$magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); -$magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); -// Remove previously generated static classes -$fs = $magentoObjectManager->create(Filesystem::class); -$fs->getDirectoryWrite(DirectoryList::ROOT)->delete('dev/tests/functional/generated/'); +deleteDirectory(MTF_BP . '/generated'); + +// Generate moduleSequence.json file +generateModuleSequence(); // Generate factories for old end-to-end tests -$magentoObjectManager->create(\Magento\Mtf\Util\Generate\Factory::class)->launch(); +$objectManager->create(\Magento\Mtf\Util\Generate\Factory::class)->launch(); $generatorPool = $objectManager->get('Magento\Mtf\Util\Generate\Pool'); foreach ($generatorPool->getGenerators() as $generator) { @@ -28,3 +24,28 @@ } \Magento\Mtf\Util\Generate\GenerateResult::displayResults(); + + +function deleteDirectory($dir) +{ + if (!file_exists($dir)) { + return true; + } + if (!is_dir($dir)) { + return unlink($dir); + } + foreach (scandir($dir) as $item) { + if ($item == '.' || $item == '..') { + continue; + } + if (!deleteDirectory($dir . DIRECTORY_SEPARATOR . $item)) { + return false; + } + } + return rmdir($dir); +} + +function generateModuleSequence() +{ + require_once "generate/moduleSequence.php"; +} diff --git a/dev/tests/functional/utils/generate/fixture.php b/dev/tests/functional/utils/generate/fixture.php index 9f454fb9dc5f1..68cdae4552261 100644 --- a/dev/tests/functional/utils/generate/fixture.php +++ b/dev/tests/functional/utils/generate/fixture.php @@ -5,6 +5,4 @@ */ require_once dirname(__DIR__) . '/' . 'bootstrap.php'; -$magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); -$magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); $objectManager->create(\Magento\Mtf\Util\Generate\Fixture::class)->launch(); diff --git a/dev/tests/functional/utils/generate/moduleSequence.php b/dev/tests/functional/utils/generate/moduleSequence.php new file mode 100644 index 0000000000000..22688d1b75820 --- /dev/null +++ b/dev/tests/functional/utils/generate/moduleSequence.php @@ -0,0 +1,20 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +require_once __DIR__ . '/../../../../../app/bootstrap.php'; + +$magentoObjectManagerFactory = \Magento\Framework\App\Bootstrap::createObjectManagerFactory(BP, $_SERVER); +$magentoObjectManager = $magentoObjectManagerFactory->create($_SERVER); +$magentoComponentSequence = $magentoObjectManager->create(\Magento\Framework\Module\ModuleList\Loader::class)->load(); +if (!file_exists(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'generated')) { + mkdir(dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'generated'); +} +file_put_contents( + dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR . 'generated' . DIRECTORY_SEPARATOR . 'moduleSequence.json', + json_encode($magentoComponentSequence, JSON_PRETTY_PRINT) +); diff --git a/dev/tests/functional/utils/generate/repository.php b/dev/tests/functional/utils/generate/repository.php index b2dc75c076f43..6633e776c9410 100644 --- a/dev/tests/functional/utils/generate/repository.php +++ b/dev/tests/functional/utils/generate/repository.php @@ -5,5 +5,4 @@ */ require_once dirname(__DIR__) . '/' . 'bootstrap.php'; -$magentoObjectManager->get(\Magento\Framework\App\State::class)->setAreaCode('frontend'); $objectManager->create(\Magento\Mtf\Util\Generate\Repository::class)->launch(); diff --git a/dev/tests/functional/utils/generateFixtureXml.php b/dev/tests/functional/utils/generateFixtureXml.php deleted file mode 100644 index 73fc266f34052..0000000000000 --- a/dev/tests/functional/utils/generateFixtureXml.php +++ /dev/null @@ -1,12 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -require_once dirname(__FILE__) . DIRECTORY_SEPARATOR . 'bootstrap.php'; - -$magentoObjectManager->create( - \Magento\Mtf\Util\Generate\Fixture\SchemaXml::class, - ['objectManager' => $magentoObjectManager] -)->launch(); diff --git a/dev/tests/functional/utils/log.php b/dev/tests/functional/utils/log.php index b6c19458b10b9..8f07d72e2a569 100644 --- a/dev/tests/functional/utils/log.php +++ b/dev/tests/functional/utils/log.php @@ -5,10 +5,13 @@ */ 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/framework/Magento/TestFramework/Annotation/IndexerDimensionMode.php b/dev/tests/integration/framework/Magento/TestFramework/Annotation/IndexerDimensionMode.php index 86d2d8ee589a7..179babdb7e184 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Annotation/IndexerDimensionMode.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Annotation/IndexerDimensionMode.php @@ -3,13 +3,14 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\TestFramework\Annotation; use Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcher; +use Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcherConfiguration; use Magento\Catalog\Model\Indexer\Product\Price\Processor; use Magento\Framework\App\Cache\TypeListInterface; -use Magento\Framework\App\Config\ConfigResource\ConfigInterface; use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\App\Config; @@ -31,9 +32,6 @@ class IndexerDimensionMode /** @var ModeSwitcher */ private $modeSwitcher; - /** @var ConfigInterface */ - private $configWriter; - /** @var ObjectManagerInterface */ private $objectManager; @@ -55,31 +53,24 @@ private function restoreDb() /** * @param string $mode + * @param TestCase $test + * @throws \Exception */ - private function setDimensionMode($mode, $test) + private function setDimensionMode(string $mode, TestCase $test) { $this->objectManager = Bootstrap::getObjectManager(); $this->modeSwitcher = $this->objectManager->get(ModeSwitcher::class); - $this->configWriter = $this->objectManager->get(ConfigInterface::class); $this->configReader = $this->objectManager->get(ScopeConfigInterface::class); $this->cacheTypeList = $this->objectManager->get(TypeListInterface::class); $this->configReader->clean(); - $previousMode = $this->configReader->getValue(ModeSwitcher::XML_PATH_PRICE_DIMENSIONS_MODE) ?: + $previousMode = $this->configReader->getValue(ModeSwitcherConfiguration::XML_PATH_PRICE_DIMENSIONS_MODE) ?: DimensionModeConfiguration::DIMENSION_NONE; if ($previousMode !== $mode) { //Create new tables and move data - $this->modeSwitcher->createTables($mode); - $this->modeSwitcher->moveData($mode, $previousMode); - - //Change config options - $this->configWriter->saveConfig(ModeSwitcher::XML_PATH_PRICE_DIMENSIONS_MODE, $mode); - $this->cacheTypeList->cleanType('config'); + $this->modeSwitcher->switchMode($mode, $previousMode); $this->objectManager->get(Config::class)->clean(); - - //Delete old tables - $this->modeSwitcher->dropTables($previousMode); } else { $this->fail('Dimensions mode for indexer has not been changed', $test); } @@ -90,6 +81,7 @@ private function setDimensionMode($mode, $test) * * @param TestCase $test * @return void + * @throws \Exception */ public function startTest(TestCase $test) { diff --git a/dev/tests/integration/framework/Magento/TestFramework/Application.php b/dev/tests/integration/framework/Magento/TestFramework/Application.php index 36609966ae139..bc6c2bc93da14 100644 --- a/dev/tests/integration/framework/Magento/TestFramework/Application.php +++ b/dev/tests/integration/framework/Magento/TestFramework/Application.php @@ -450,6 +450,7 @@ public function install($cleanup) $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::VAR_DIR][DirectoryList::PATH]); $this->copyAppConfigFiles(); + $this->copyGlobalConfigFile(); $installParams = $this->getInstallCliParams(); @@ -509,6 +510,17 @@ private function copyAppConfigFiles() } } } + + /** + * Copies global configuration file from the tests folder (see TESTS_GLOBAL_CONFIG_FILE) + * + * @return void + */ + private function copyGlobalConfigFile() + { + $targetFile = $this->_configDir . '/config.local.php'; + copy($this->globalConfigFile, $targetFile); + } /** * Gets a list of CLI params for installation diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php index 2c9376863af7b..800d41f3c786f 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery/ContentTest.php @@ -63,7 +63,6 @@ public function testGetUploader() * * @dataProvider getImagesAndImageTypesDataProvider * @magentoDataFixture Magento/Catalog/_files/product_with_image.php - * @magentoDataFixture Magento/Catalog/Fixtures/product_with_image.php * @magentoAppIsolation enabled * @param bool $isProductNew * @return void @@ -73,13 +72,12 @@ public function testGetImagesJson(bool $isProductNew) $this->prepareProduct($isProductNew); $imagesJson = $this->block->getImagesJson(); $images = json_decode($imagesJson); - self::assertEquals(2, count($images)); - foreach ($images as $image) { - self::assertRegExp('/\/m\/a\/magento_image/', $image->file); - self::assertSame('image', $image->media_type); - self::assertRegExp('/Image Alt Text/', $image->label); - self::assertRegExp('/\/pub\/media\/catalog\/product\/m\/a\/magento_image/', $image->url); - } + $image = array_shift($images); + $this->assertRegExp('/\/m\/a\/magento_image/', $image->file); + $this->assertSame('image', $image->media_type); + $this->assertSame('Image Alt Text', $image->label); + $this->assertSame('Image Alt Text', $image->label_default); + $this->assertRegExp('/\/pub\/media\/catalog\/product\/m\/a\/magento_image/', $image->url); } /** diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Fixtures/product_with_image.php b/dev/tests/integration/testsuite/Magento/Catalog/Fixtures/product_with_image.php deleted file mode 100644 index ade178bf2334a..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Catalog/Fixtures/product_with_image.php +++ /dev/null @@ -1,41 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -declare(strict_types=1); - -use Magento\Catalog\Api\ProductRepositoryInterface; -use Magento\Catalog\Model\Product; -use Magento\TestFramework\Helper\Bootstrap; -use Magento\TestFramework\ObjectManager; - -require __DIR__ . '/../_files/product_image.php'; -require __DIR__ . '/../_files/product_simple.php'; - -/** @var ObjectManager $objectManager */ -$objectManager = Bootstrap::getObjectManager(); -$productRepository = $objectManager->create(ProductRepositoryInterface::class); -$product = $productRepository->get('simple'); - -/** @var Product $product */ -$product->setStoreId(1) - ->setImage('/m/a/magento_image1.jpg') - ->setSmallImage('/m/a/magento_image1.jpg') - ->setThumbnail('/m/a/magento_image1.jpg') - ->setData( - 'media_gallery', - [ - 'images' => [ - [ - 'file' => '/m/a/magento_image.jpg', - 'position' => 1, - 'label' => 'Image Alt Text 1', - 'disabled' => 0, - 'media_type' => 'image' - ], - ] - ] - ) - ->setCanSaveCustomOptions(true) - ->save(); diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Fixtures/product_with_image_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/Fixtures/product_with_image_rollback.php deleted file mode 100644 index 02ee6e02dde5b..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Catalog/Fixtures/product_with_image_rollback.php +++ /dev/null @@ -1,8 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -require __DIR__ . '/../_files/product_simple_rollback.php'; -require __DIR__ . '/../_files/product_image_rollback.php'; diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ImageUploaderTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ImageUploaderTest.php index 723ff963e2bfc..ea151c2f68750 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ImageUploaderTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ImageUploaderTest.php @@ -46,7 +46,8 @@ protected function setUp() [ 'baseTmpPath' => $this->mediaDirectory->getRelativePath('tmp'), 'basePath' => __DIR__, - 'allowedExtensions' => ['jpg', 'jpeg', 'gif', 'png'] + 'allowedExtensions' => ['jpg', 'jpeg', 'gif', 'png'], + 'allowedMimeTypes' => ['image/jpg', 'image/jpeg', 'image/gif', 'image/png'] ] ); } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFileTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFileTest.php index 4a8ff84ecfaa5..191432d36c7ec 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFileTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFileTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Catalog\Model\Product\Option\Type\File; +use Magento\Framework\Math\Random; + /** * @magentoDataFixture Magento/Catalog/_files/validate_image.php * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -47,11 +49,18 @@ protected function setUp() $fileSize = $this->objectManager->create(\Magento\Framework\File\Size::class); $this->maxFileSize = $fileSize->getMaxFileSize(); $this->maxFileSizeInMb = $fileSize->getMaxFileSizeInMb(); + $random = $this->getMockBuilder(Random::class) + ->disableOriginalConstructor() + ->getMock(); + $random->expects($this->any()) + ->method('getRandomString') + ->willReturn('RandomString'); $this->model = $this->objectManager->create( - \Magento\Catalog\Model\Product\Option\Type\File\ValidatorFile::class, + ValidatorFile::class, [ - 'httpFactory' => $this->httpFactoryMock + 'httpFactory' => $this->httpFactoryMock, + 'random' => $random, ] ); } @@ -339,8 +348,8 @@ protected function expectedValidate() return [ 'type' => 'image/jpeg', 'title' => 'test.jpg', - 'quote_path' => 'custom_options/quote/t/e/a071b9ffc8fda6df1652c05a4c61bf8a.jpg', - 'order_path' => 'custom_options/order/t/e/a071b9ffc8fda6df1652c05a4c61bf8a.jpg', + 'quote_path' => 'custom_options/quote/t/e/RandomString', + 'order_path' => 'custom_options/order/t/e/RandomString', 'size' => '3046', 'width' => 136, 'height' => 131, diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index 62a918b7e3c5a..2a05bf3281c4b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -554,7 +554,7 @@ public function testGetOptions() continue; } foreach ($option->getValues() as $value) { - $this->assertEquals($expectedValue[$value->getSku()], floatval($value->getPrice())); + $this->assertEquals($expectedValue[$value->getSku()], (float)$value->getPrice()); } } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php index a5e55e181cbaf..6ac7a8551fd99 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/Product/CollectionTest.php @@ -127,6 +127,36 @@ public function testGetProductsWithTierPrice() $this->assertEquals(5, $tierPrices[2]->getValue()); } + /** + * @magentoDataFixture Magento/Catalog/_files/products.php + * @magentoDbIsolation disabled + */ + public function testGetProductsWithSpecialPrice() + { + $product = $this->productRepository->get('simple'); + $originalFinalPrice = $product->getFinalPrice(); + + $specialPrice = 9; + $product->setSpecialPrice($specialPrice); + $product = $this->productRepository->save($product); + /** @var \Magento\Catalog\Model\Product $item */ + $item = $this->collection->addIdFilter($product->getId()) + ->addPriceData() + ->getFirstItem(); + $item->setPriceCalculation(false); + $this->assertEquals($specialPrice, $item->getFinalPrice()); + + $product->setSpecialPrice(null); + $product = $this->productRepository->save($product); + /** @var \Magento\Catalog\Model\Product $item */ + $item = $this->collection->clear() + ->addIdFilter($product->getId()) + ->addPriceData() + ->getFirstItem(); + $item->setPriceCalculation(false); + $this->assertEquals($originalFinalPrice, $item->getFinalPrice()); + } + /** * Test addAttributeToSort() with attribute 'is_saleable' works properly on frontend. * @@ -187,4 +217,15 @@ public function testJoinTable() self::assertContains($expected, str_replace(PHP_EOL, '', $sql)); } + + /** + * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/few_simple_products.php + * @magentoDbIsolation enabled + */ + public function testAddAttributeToFilterAffectsGetSize() + { + $this->assertEquals(10, $this->collection->getSize()); + $this->collection->addAttributeToFilter('sku', 'Product1'); + $this->assertEquals(1, $this->collection->getSize()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/few_simple_products.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/few_simple_products.php new file mode 100644 index 0000000000000..d8b24855ed1f6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/few_simple_products.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Model\ProductFactory; +use Magento\Catalog\Api\ProductRepositoryInterface; + +/** @var Magento\Framework\ObjectManagerInterface $objcetManager */ +$objectManager = Bootstrap::getObjectManager(); + +/** @var ProductFactory $productFactory */ +$productFactory = $objectManager->create(ProductFactory::class); + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = $objectManager->create(ProductRepositoryInterface::class); + +// Create 10 products (with change this variable, don't forget to change the same in rollback) +$productsAmount = 10; + +for ($i = 1; $i <= $productsAmount; $i++) { + $productArray = [ + 'data' => [ + 'name' => "Product{$i}", + 'sku' => "Product{$i}", + 'price' => 100, + 'attribute_set_id' => 4, + 'website_ids' => [1] + ] + ]; + + $productRepository->save($productFactory->create($productArray)); +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/few_simple_products_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/few_simple_products_rollback.php new file mode 100644 index 0000000000000..afe9483f63e19 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/_files/few_simple_products_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Exception\NoSuchEntityException; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager() + ->get(ProductRepositoryInterface::class); + +/** + * Delete 10 products + */ +$productsAmount = 10; + +try { + for ($i = 1; $i <= $productsAmount; $i++) { + /** @var \Magento\Catalog\Api\Data\ProductInterface $product */ + $product = $productRepository->get("Product{$i}", false, null, true); + $productRepository->delete($product); + } +} catch (NoSuchEntityException $e) { +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/FinalPriceBoxTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/FinalPriceBoxTest.php index c6d6c3cf42214..88d3c3a6c9b1a 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/FinalPriceBoxTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Pricing/Render/FinalPriceBoxTest.php @@ -125,7 +125,7 @@ protected function setUp() /** * @magentoDataFixture Magento/Catalog/_files/product_has_tier_price_show_as_low_as.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ public function testRenderAmountMinimalProductWithTierPricesShouldShowMinTierPrice() @@ -136,7 +136,7 @@ public function testRenderAmountMinimalProductWithTierPricesShouldShowMinTierPri /** * @magentoDataFixture Magento/Catalog/_files/product_different_store_prices.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled * @magentoConfigFixture current_store catalog/frontend/flat_catalog_product 1 */ diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php index 0251c2b4c34cb..a9c24829f9ad8 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -1568,7 +1568,7 @@ public function testExistingProductWithUrlKeys() /** * @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php - * @magentoDbIsolation disabled + * @magentoDbIsolation enabled * @magentoAppIsolation enabled */ public function testImportWithoutUrlKeys() @@ -1603,6 +1603,82 @@ public function testImportWithoutUrlKeys() } } + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + * @return void + */ + public function testImportWithUrlKeysWithSpaces() + { + $products = [ + 'simple1' => 'url-key-with-spaces1', + 'simple2' => 'url-key-with-spaces2', + ]; + $filesystem = $this->objectManager->create(\Magento\Framework\Filesystem::class); + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => __DIR__ . '/_files/products_to_import_with_url_keys_with_spaces.csv', + 'directory' => $directory, + ] + ); + + $errors = $this->_model->setParameters( + ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_ADD_UPDATE, 'entity' => 'catalog_product'] + ) + ->setSource($source) + ->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 0); + $this->_model->importData(); + + $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + foreach ($products as $productSku => $productUrlKey) { + $this->assertEquals($productUrlKey, $productRepository->get($productSku)->getUrlKey()); + } + } + + /** + * Make sure the absence of a url_key column in the csv file won't erase the url key of the existing products. + * To reach the goal we need to not send the name column, as the url key is generated from it. + * + * @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testImportWithoutUrlKeysAndName() + { + $products = [ + 'simple1' => 'url-key', + 'simple2' => 'url-key2', + ]; + $filesystem = $this->objectManager->create(\Magento\Framework\Filesystem::class); + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => __DIR__ . '/_files/products_to_import_without_url_keys_and_name.csv', + 'directory' => $directory + ] + ); + + $errors = $this->_model->setParameters( + ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product'] + ) + ->setSource($source) + ->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 0); + $this->_model->importData(); + + $productRepository = $this->objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class); + foreach ($products as $productSku => $productUrlKey) { + $this->assertEquals($productUrlKey, $productRepository->get($productSku)->getUrlKey()); + } + } + /** * @magentoAppIsolation enabled */ @@ -2229,4 +2305,52 @@ private function importFile(string $fileName) $this->_model->importData(); } + + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + * @magentoDbIsolation disabled + * @magentoAppIsolation enabled + */ + public function testImportProductWithUpdateUrlKey() + { + $filesystem = $this->objectManager->create(Filesystem::class); + $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT); + $source = $this->objectManager->create( + \Magento\ImportExport\Model\Import\Source\Csv::class, + [ + 'file' => __DIR__ . '/_files/product_for_update_url_key.csv', + 'directory' => $directory, + ] + ); + + $errors = $this->_model->setParameters( + ['behavior' => Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product'] + )->setSource( + $source + )->validateData(); + + $this->assertTrue($errors->getErrorsCount() == 0); + $this->_model->importData(); + + /** + * @var $repository \Magento\Catalog\Model\ProductRepository + */ + $repository = $this->objectManager->create(\Magento\Catalog\Model\ProductRepository::class); + $product = $repository->get('simple'); + + $repUrlRewriteCol = $this->objectManager->create( + \Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection::class + ); + $collUrlRewrite = $repUrlRewriteCol->addFieldToSelect(['request_path']) + ->addFieldToFilter('entity_id', ['eq'=> $product->getEntityId()]) + ->addFieldToFilter('entity_type', ['eq'=> 'product']) + ->load(); + + $this->assertCount(1, $collUrlRewrite); + + $this->assertEquals( + sprintf('%s.html', $product->getUrlKey()), + $collUrlRewrite->getFirstItem()->getRequestPath() + ); + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_for_update_url_key.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_for_update_url_key.csv new file mode 100644 index 0000000000000..eb7daab7481a0 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_for_update_url_key.csv @@ -0,0 +1,2 @@ +sku,url_key +simple,simple-tester-url diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_url_keys_with_spaces.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_url_keys_with_spaces.csv new file mode 100644 index 0000000000000..01845c3cdc9d5 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_url_keys_with_spaces.csv @@ -0,0 +1,3 @@ +sku,product_type,store_view_code,name,price,attribute_set_code,url_key +simple1,simple,,"simple1",50,Default,url key with spaces1 +simple2,simple,,"simple2",55,Default,url key with spaces2 diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_without_url_keys_and_name.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_without_url_keys_and_name.csv new file mode 100644 index 0000000000000..8ea6ab92a0295 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_without_url_keys_and_name.csv @@ -0,0 +1,3 @@ +sku,price +simple1,25 +simple2,34 diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Model/SessionTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Model/SessionTest.php index 57cbaef54e589..af572c556bb07 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/Model/SessionTest.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/Model/SessionTest.php @@ -5,19 +5,50 @@ */ namespace Magento\Checkout\Model; +use Magento\Catalog\Api\Data\ProductTierPriceInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\Api\FilterBuilder; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Quote\Api\Data\CartInterface; use Magento\TestFramework\Helper\Bootstrap; +/** + * Class SessionTest + */ class SessionTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Checkout\Model\Session + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + /** + * @var CustomerRepositoryInterface */ - protected $_checkoutSession; + private $customerRepository; + /** + * @var CustomerSession + */ + private $customerSession; + + /** + * @var Session + */ + private $checkoutSession; + + /** + * @return void + */ protected function setUp() { - $this->_checkoutSession = Bootstrap::getObjectManager()->create(\Magento\Checkout\Model\Session::class); - parent::setUp(); + $this->objectManager = Bootstrap::getObjectManager(); + $this->customerRepository = $this->objectManager->create(CustomerRepositoryInterface::class); + $this->customerSession = $this->objectManager->get(CustomerSession::class); + $this->checkoutSession = $this->objectManager->create(Session::class); } /** @@ -29,15 +60,11 @@ protected function setUp() */ public function testGetQuoteNotInitializedCustomerSet() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ - $customerRepository = $objectManager->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); - $customer = $customerRepository->getById(1); - $this->_checkoutSession->setCustomerData($customer); + $customer = $this->customerRepository->getById(1); + $this->checkoutSession->setCustomerData($customer); /** Execute SUT */ - $quote = $this->_checkoutSession->getQuote(); + $quote = $this->checkoutSession->getQuote(); $this->_validateCustomerDataInQuote($quote); } @@ -51,17 +78,11 @@ public function testGetQuoteNotInitializedCustomerSet() */ public function testGetQuoteNotInitializedCustomerLoggedIn() { - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ - $customerRepository = $objectManager->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); - $customer = $customerRepository->getById(1); - /** @var \Magento\Customer\Model\Session $customerSession */ - $customerSession = Bootstrap::getObjectManager()->get(\Magento\Customer\Model\Session::class); - $customerSession->setCustomerDataObject($customer); + $customer = $this->customerRepository->getById(1); + $this->customerSession->setCustomerDataObject($customer); /** Execute SUT */ - $quote = $this->_checkoutSession->getQuote(); + $quote = $this->checkoutSession->getQuote(); $this->_validateCustomerDataInQuote($quote); } @@ -80,29 +101,86 @@ public function testGetQuoteNotInitializedCustomerLoggedIn() */ public function testLoadCustomerQuoteCustomerWithoutQuote() { - $quote = $this->_checkoutSession->getQuote(); + $quote = $this->checkoutSession->getQuote(); $this->assertEmpty($quote->getCustomerId(), 'Precondition failed: Customer data must not be set to quote'); $this->assertEmpty($quote->getCustomerEmail(), 'Precondition failed: Customer data must not be set to quote'); - $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); - - /** @var \Magento\Customer\Api\CustomerRepositoryInterface $customerRepository */ - $customerRepository = $objectManager->create(\Magento\Customer\Api\CustomerRepositoryInterface::class); - $customer = $customerRepository->getById(1); - /** @var \Magento\Customer\Model\Session $customerSession */ - $customerSession = Bootstrap::getObjectManager()->get(\Magento\Customer\Model\Session::class); - $customerSession->setCustomerDataObject($customer); + $customer = $this->customerRepository->getById(1); + $this->customerSession->setCustomerDataObject($customer); /** Ensure that customer data is still unavailable before SUT invocation */ - $quote = $this->_checkoutSession->getQuote(); + $quote = $this->checkoutSession->getQuote(); $this->assertEmpty($quote->getCustomerEmail(), 'Precondition failed: Customer data must not be set to quote'); /** Execute SUT */ - $this->_checkoutSession->loadCustomerQuote(); - $quote = $this->_checkoutSession->getQuote(); + $this->checkoutSession->loadCustomerQuote(); + $quote = $this->checkoutSession->getQuote(); $this->_validateCustomerDataInQuote($quote); } + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + * @magentoDataFixture Magento/Sales/_files/quote.php + */ + public function testGetQuoteWithProductWithTierPrice() + { + $reservedOrderId = 'test01'; + $customerGroupId = 1; + $tierPriceQty = 1; + $tierPriceValue = 9; + + $productRepository = $this->objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get('simple'); + $tierPrice = $this->objectManager->create(ProductTierPriceInterface::class) + ->setCustomerGroupId($customerGroupId) + ->setQty($tierPriceQty) + ->setValue($tierPriceValue); + $product->setTierPrices([$tierPrice]); + $productRepository->save($product); + + $quote = $this->getQuote($reservedOrderId); + $this->checkoutSession->setQuoteId($quote->getId()); + + $quote = $this->checkoutSession->getQuote(); + $item = $quote->getItems()[0]; + /** @var \Magento\Catalog\Model\Product $quoteProduct */ + $quoteProduct = $item->getProduct(); + $this->assertEquals(10, $quoteProduct->getTierPrice($tierPriceQty)); + + $customer = $this->customerRepository->getById(1); + $this->customerSession->setCustomerDataAsLoggedIn($customer); + + $quote = $this->checkoutSession->getQuote(); + $item = $quote->getItems()[0]; + /** @var \Magento\Catalog\Model\Product $quoteProduct */ + $quoteProduct = $item->getProduct(); + $this->assertEquals($tierPriceValue, $quoteProduct->getTierPrice(1)); + } + + /** + * Returns quote by reserved order id. + * + * @param string $reservedOrderId + * @return CartInterface + */ + private function getQuote(string $reservedOrderId): CartInterface + { + $filterBuilder = $this->objectManager->create(FilterBuilder::class); + $filter = $filterBuilder->setField('reserved_order_id') + ->setConditionType('=') + ->setValue($reservedOrderId) + ->create(); + $searchCriteriaBuilder = $this->objectManager->create(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilters([$filter]) + ->create(); + $quoteRepository = $this->objectManager->get(CartRepositoryInterface::class); + $searchResult = $quoteRepository->getList($searchCriteria); + /** @var CartInterface[] $items */ + $items = $searchResult->getItems(); + + return \array_values($items)[0]; + } + /** * Ensure that quote has customer data specified in customer fixture. * diff --git a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php index 4ba1b44010c34..6ceb55ab502a6 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Block/Adminhtml/Edit/Tab/View/PersonalInfoTest.php @@ -110,9 +110,9 @@ public function testGetCustomer() \Magento\Customer\Api\Data\CustomerInterface::class ); foreach ($expectedCustomerData as $property => $value) { - $expectedValue = is_numeric($value) ? intval($value) : $value; + $expectedValue = is_numeric($value) ? (int)$value : $value; $actualValue = isset($actualCustomerData[$property]) ? $actualCustomerData[$property] : null; - $actualValue = is_numeric($actualValue) ? intval($actualValue) : $actualValue; + $actualValue = is_numeric($actualValue) ? (int)$actualValue : $actualValue; $this->assertEquals($expectedValue, $actualValue); } } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index 154cc0e5b4033..ff0d838cb6f82 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -128,7 +128,6 @@ public function testCreatepasswordActionWithDirectLink() $customer->save(); $this->getRequest()->setParam('token', $token); - $this->getRequest()->setParam('id', $customer->getId()); $this->dispatch('customer/account/createPassword'); @@ -136,12 +135,13 @@ public function testCreatepasswordActionWithDirectLink() $this->assertEquals(302, $response->getHttpResponseCode()); $text = $response->getBody(); $this->assertFalse((bool)preg_match('/' . $token . '/m', $text)); - $this->assertRedirect($this->stringContains('customer/account/createpassword')); + $this->assertRedirect( + $this->stringContains('customer/account/createpassword') + ); /** @var Session $customer */ $session = Bootstrap::getObjectManager()->get(Session::class); $this->assertEquals($token, $session->getRpToken()); - $this->assertEquals($customer->getId(), $session->getRpCustomerId()); $this->assertNotContains($token, $response->getHeader('Location')->getFieldValue()); } @@ -596,7 +596,7 @@ public function testMissingDataEditPostAction() $this->assertRedirect($this->stringContains('customer/account/edit/')); $this->assertSessionMessages( - $this->equalTo(['"Email" is not a valid email address.']), + $this->equalTo(['"Email" is not a valid email address.']), MessageInterface::TYPE_ERROR ); } @@ -655,7 +655,7 @@ public function testWrongConfirmationEditPostAction() $this->assertRedirect($this->stringContains('customer/account/edit/')); $this->assertSessionMessages( - $this->equalTo(['Password confirmation doesn\'t match entered password.']), + $this->equalTo(['Password confirmation doesn't match entered password.']), MessageInterface::TYPE_ERROR ); } diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php index 7c64c946e7db9..434e24b7d2771 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassAssignGroupTest.php @@ -63,13 +63,18 @@ public function testMassAssignGroupAction() $customer = $this->customerRepository->get($customerEmail); $this->assertEquals(1, $customer->getGroupId()); + /** @var \Magento\Framework\Data\Form\FormKey $formKey */ + $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); + $params = [ 'group' => 0, 'namespace' => 'customer_listing', - 'selected' => [$customer->getId()] + 'selected' => [$customer->getId()], + 'form_key' => $formKey->getFormKey() ]; $this->getRequest()->setParams($params); + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( self::equalTo(['A total of 1 record(s) were updated.']), @@ -96,14 +101,18 @@ public function testLargeGroupMassAssignGroupAction() $this->assertEquals(1, $customer->getGroupId()); $ids[] = $customer->getId(); } + /** @var \Magento\Framework\Data\Form\FormKey $formKey */ + $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); $params = [ 'group' => 0, 'namespace' => 'customer_listing', 'selected' => $ids, + 'form_key' => $formKey->getFormKey() ]; $this->getRequest()->setParams($params); + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( self::equalTo(['A total of 5 record(s) were updated.']), @@ -124,11 +133,17 @@ public function testLargeGroupMassAssignGroupAction() */ public function testMassAssignGroupActionNoCustomerIds() { + /** @var \Magento\Framework\Data\Form\FormKey $formKey */ + $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); + $params = [ 'group' => 0, 'namespace' => 'customer_listing', + 'form_key' => $formKey->getFormKey() ]; + $this->getRequest()->setParams($params); + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/customer/index/massAssignGroup'); $this->assertSessionMessages( $this->equalTo(['Please select item(s).']), diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php index 2a916c1c00c66..96e993932cb18 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassDeleteTest.php @@ -101,12 +101,17 @@ public function testSuccessMassDeleteAction(array $emails, Constraint $constrain */ private function massDeleteAssertions($ids, Constraint $constraint, $messageType) { + /** @var \Magento\Framework\Data\Form\FormKey $formKey */ + $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); + $requestData = [ 'selected' => $ids, 'namespace' => 'customer_listing', + 'form_key' => $formKey->getFormKey() ]; $this->getRequest()->setParams($requestData); + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/customer/index/massDelete'); $this->assertSessionMessages( $constraint, diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php index e4b9eed2e53d5..579b1a67ecb21 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/Index/MassSubscribeTest.php @@ -68,14 +68,20 @@ public function testMassSubscriberAction() /** @var CustomerInterface $customer2 */ $customer2 = $customerRepository->get('customer2@example.com'); + /** @var \Magento\Framework\Data\Form\FormKey $formKey */ + $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); + $params = [ 'selected' => [ $customer1->getId(), $customer2->getId(), ], 'namespace' => 'customer_listing', + 'form_key' => $formKey->getFormKey() ]; + $this->getRequest()->setParams($params); + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/customer/index/massSubscribe'); @@ -105,11 +111,16 @@ public function testMassSubscriberAction() */ public function testMassSubscriberActionNoSelection() { + /** @var \Magento\Framework\Data\Form\FormKey $formKey */ + $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); + $params = [ - 'namespace' => 'customer_listing' + 'namespace' => 'customer_listing', + 'form_key' => $formKey->getFormKey() ]; $this->getRequest()->setParams($params); + $this->getRequest()->setMethod('POST'); $this->dispatch('backend/customer/index/massSubscribe'); $this->assertRedirect($this->stringStartsWith($this->baseControllerUrl)); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php index 865a190f077b2..2e132d27f5cb1 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/AccountManagementTest.php @@ -357,6 +357,32 @@ public function testValidateResetPasswordLinkTokenNull() } } + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testValidateResetPasswordLinkTokenWithoutId() + { + $token = 'randomStr123'; + $this->setResetPasswordData($token, 'Y-m-d H:i:s'); + + $this->assertTrue( + $this->accountManagement->validateResetPasswordLinkToken(0, $token) + ); + } + + /** + * @magentoDataFixture Magento/Customer/_files/two_customers.php + * @expectedException \Magento\Framework\Exception\State\ExpiredException + */ + public function testValidateResetPasswordLinkTokenAmbiguous() + { + $token = 'randomStr123'; + $this->setResetPasswordData($token, 'Y-m-d H:i:s', 1); + $this->setResetPasswordData($token, 'Y-m-d H:i:s', 2); + + $this->accountManagement->validateResetPasswordLinkToken(0, $token); + } + /** * @magentoAppArea frontend * @magentoDataFixture Magento/Customer/_files/customer.php @@ -512,6 +538,34 @@ public function testResetPasswordTokenInvalidUserEmail() } } + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testResetPasswordWithoutEmail() + { + $resetToken = 'lsdj579slkj5987slkj595lkj'; + $password = 'new_Password123'; + + $this->setResetPasswordData($resetToken, 'Y-m-d H:i:s'); + $this->assertTrue( + $this->accountManagement->resetPassword('', $resetToken, $password) + ); + } + + /** + * @magentoDataFixture Magento/Customer/_files/two_customers.php + * @expectedException \Magento\Framework\Exception\State\ExpiredException + */ + public function testResetPasswordAmbiguousToken() + { + $resetToken = 'lsdj579slkj5987slkj595lkj'; + $password = 'new_Password123'; + + $this->setResetPasswordData($resetToken, 'Y-m-d H:i:s', 1); + $this->setResetPasswordData($resetToken, 'Y-m-d H:i:s', 2); + $this->accountManagement->resetPassword('', $resetToken, $password); + } + /** * @magentoAppArea frontend * @magentoAppIsolation enabled @@ -960,10 +1014,13 @@ public function testGetDefaultAddressesForNonExistentAddress() * * @param $resetToken * @param $date + * @param int $customerIdFromFixture Which customer to use. */ - protected function setResetPasswordData($resetToken, $date) - { - $customerIdFromFixture = 1; + protected function setResetPasswordData( + $resetToken, + $date, + int $customerIdFromFixture = 1 + ) { /** @var \Magento\Customer\Model\Customer $customerModel */ $customerModel = $this->objectManager->create(\Magento\Customer\Model\Customer::class); $customerModel->load($customerIdFromFixture); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php index ba7df011eec9b..3d88ba391ee0a 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest/SourceClassWithNamespace.php @@ -110,4 +110,16 @@ public static function publicChildStatic() final public function publicChildFinal() { } + + /** + * Test method + * + * @param bool $arg + * @return SourceClassWithNamespace + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function publicWithSelf($arg = false): self + { + } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample index cf8611866a885..3e0389052ac07 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceInterceptor.php.sample @@ -54,6 +54,19 @@ class Interceptor extends \Magento\Framework\Code\GeneratorTest\SourceClassWithN } } + /** + * {@inheritdoc} + */ + public function publicWithSelf($arg = false) : \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespace + { + $pluginInfo = $this->pluginList->getNext($this->subjectType, 'publicWithSelf'); + if (!$pluginInfo) { + return parent::publicWithSelf($arg); + } else { + return $this->___callPlugins('publicWithSelf', func_get_args(), $pluginInfo); + } + } + /** * {@inheritdoc} */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample index 345ac49c6a4f4..30112dcb51485 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample +++ b/dev/tests/integration/testsuite/Magento/Framework/Code/_expected/SourceClassWithNamespaceProxy.php.sample @@ -114,6 +114,14 @@ class Proxy extends \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespa return $this->_getSubject()->publicChildWithoutParameters(); } + /** + * {@inheritdoc} + */ + public function publicWithSelf($arg = false) : \Magento\Framework\Code\GeneratorTest\SourceClassWithNamespace + { + return $this->_getSubject()->publicWithSelf($arg); + } + /** * {@inheritdoc} */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Composer/RemoveTest.php b/dev/tests/integration/testsuite/Magento/Framework/Composer/RemoveTest.php index bfe8264d7cce6..211b28617cbe2 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Composer/RemoveTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Composer/RemoveTest.php @@ -24,7 +24,7 @@ public function testRemove() [ 'command' => 'remove', 'packages' => ['magento/package-a', 'magento/package-b'], - '--no-update' => true, + '--no-update-with-dependencies' => true, ] ); $composerAppFactory->expects($this->once()) diff --git a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Driver/FileTest.php b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Driver/FileTest.php index b74d87fab3a84..a9e755313895c 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Driver/FileTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Filesystem/Driver/FileTest.php @@ -9,23 +9,29 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; +use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\TestFramework\Helper\Bootstrap; class FileTest extends \PHPUnit\Framework\TestCase { /** - * @var \Magento\Framework\Filesystem\Driver\File + * @var File */ - protected $driver; + private $driver; /** - * @var string + * @var String */ - protected $absolutePath; + private $absolutePath; /** - * get relative path for test + * @var String + */ + private $generatedPath; + + /** + * Returns relative path for the test. * * @param $relativePath * @return string @@ -36,16 +42,26 @@ protected function getTestPath($relativePath) } /** - * Set up + * @inheritdoc */ public function setUp() { - $this->driver = new \Magento\Framework\Filesystem\Driver\File(); + $this->driver = new File(); $this->absolutePath = dirname(__DIR__) . '/_files/'; + $this->generatedPath = $this->getTestPath('generated'); + $this->removeGeneratedDirectory(); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->removeGeneratedDirectory(); } /** - * test read recursively read + * Tests directory recursive read. */ public function testReadDirectoryRecursively() { @@ -63,7 +79,7 @@ public function testReadDirectoryRecursively() } /** - * test exception + * Tests directory reading exception. * * @expectedException \Magento\Framework\Exception\FileSystemException */ @@ -72,6 +88,11 @@ public function testReadDirectoryRecursivelyFailure() $this->driver->readDirectoryRecursively($this->getTestPath('not-existing-directory')); } + /** + * Tests of directory creating. + * + * @throws FileSystemException + */ public function testCreateDirectory() { $generatedPath = $this->getTestPath('generated/roo/bar/baz/foo'); @@ -123,4 +144,39 @@ public function createFileDataProvider() ] ]; } + + /** + * Tests creation and removing of symlinks. + * + * @throws FileSystemException + * @return void + */ + public function testSymlinks() + { + $sourceDirectory = $this->generatedPath . '/source'; + $destinationDirectory = $this->generatedPath . '/destination'; + + $this->driver->createDirectory($sourceDirectory); + $this->driver->createDirectory($destinationDirectory); + + $linkName = $destinationDirectory . '/link'; + + self::assertTrue($this->driver->isWritable($destinationDirectory)); + self::assertTrue($this->driver->symlink($sourceDirectory, $linkName)); + self::assertTrue($this->driver->isExists($linkName)); + self::assertTrue($this->driver->deleteDirectory($linkName)); + } + + /** + * Remove generated directories. + * + * @throws FileSystemException + * @return void + */ + private function removeGeneratedDirectory() + { + if (is_dir($this->generatedPath)) { + $this->driver->deleteDirectory($this->generatedPath); + } + } } diff --git a/dev/tests/integration/testsuite/Magento/Framework/Mail/MessageTest.php b/dev/tests/integration/testsuite/Magento/Framework/Mail/MessageTest.php deleted file mode 100644 index d2b220b38f695..0000000000000 --- a/dev/tests/integration/testsuite/Magento/Framework/Mail/MessageTest.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\Framework\Mail; - -class MessageTest extends \PHPUnit\Framework\TestCase -{ - /** - * @var Message - */ - private $message; - - /** - * @inheritdoc - */ - protected function setUp() - { - $this->message = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->create(Message::class); - } - - public function testGetHeaderEncodingDefaultValue() - { - $this->assertEquals(\Zend_Mime::ENCODING_BASE64, $this->message->getHeaderEncoding()); - } - - public function testGetCharsetDefaultValue() - { - $this->assertEquals('utf-8', $this->message->getCharset()); - } -} diff --git a/dev/tests/integration/testsuite/Magento/GiftMessage/Observer/SalesEventQuoteMergeTest.php b/dev/tests/integration/testsuite/Magento/GiftMessage/Observer/SalesEventQuoteMergeTest.php new file mode 100644 index 0000000000000..0902c35568ee3 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/GiftMessage/Observer/SalesEventQuoteMergeTest.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GiftMessage\Observer; + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Event\ManagerInterface; +use Magento\Quote\Model\QuoteFactory; +use Magento\Quote\Model\Quote; + +class SalesEventQuoteMergeTest extends \PHPUnit\Framework\TestCase +{ + /** + * @magentoAppArea frontend + */ + public function testQuoteMerge() + { + $giftMessageId = 6; + $objectManager = Bootstrap::getObjectManager(); + $eventManager = $objectManager->get(ManagerInterface::class); + /** @var Quote $sourceQuote */ + $sourceQuote = $objectManager->create(QuoteFactory::class)->create(); + $targetQuote = clone($sourceQuote); + $sourceQuote->setGiftMessageId($giftMessageId); + + $eventManager->dispatch( + 'sales_quote_merge_after', + [ + 'quote' => $targetQuote, + 'source' => $sourceQuote + ] + ); + + self::assertEquals($giftMessageId, $targetQuote->getGiftMessageId()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Integration/Model/Config/Consolidated/_files/integration.php b/dev/tests/integration/testsuite/Magento/Integration/Model/Config/Consolidated/_files/integration.php index cc2841f1acf2c..d72a1359dfa52 100644 --- a/dev/tests/integration/testsuite/Magento/Integration/Model/Config/Consolidated/_files/integration.php +++ b/dev/tests/integration/testsuite/Magento/Integration/Model/Config/Consolidated/_files/integration.php @@ -9,6 +9,7 @@ 'endpoint_url' => 'http://example.com/endpoint1', 'identity_link_url' => 'http://www.example.com/identity1', 'resource' => [ + 'Magento_Backend::admin', 'Magento_Customer::customer', 'Magento_Customer::manage', 'Magento_Sales::sales', @@ -26,6 +27,7 @@ 'endpoint_url' => 'http://example.com/integration2', 'identity_link_url' => 'http://www.example.com/identity2', 'resource' => [ + 'Magento_Backend::admin', 'Magento_Sales::sales', 'Magento_Sales::sales_operation', 'Magento_Sales::sales_order', @@ -40,6 +42,7 @@ 'TestIntegration3' => [ 'email' => 'test-integration3@example.com', 'resource' => [ + 'Magento_Backend::admin', 'Magento_Sales::sales', 'Magento_Sales::sales_operation', 'Magento_Sales::sales_order', diff --git a/dev/tests/integration/testsuite/Magento/Integration/Model/ConfigBasedIntegrationManagerTest.php b/dev/tests/integration/testsuite/Magento/Integration/Model/ConfigBasedIntegrationManagerTest.php new file mode 100644 index 0000000000000..af1ee643866f6 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Integration/Model/ConfigBasedIntegrationManagerTest.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Integration\Model; + +/** + * Test class for \Magento\Integration\Model\ConfigBasedIntegrationManager.php. + */ +class ConfigBasedIntegrationManagerTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + protected $consolidatedMock; + + /** + * @var \Magento\Integration\Model\ConfigBasedIntegrationManager + */ + protected $integrationManager; + + /** + * @var \Magento\Integration\Api\IntegrationServiceInterface + */ + protected $integrationService; + + /** + * @var \Magento\TestFramework\ObjectManager + */ + protected $objectManager; + + /** + * @inheritdoc + */ + protected function setUp() + { + parent::setUp(); + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->consolidatedMock = $this->createMock(\Magento\Integration\Model\ConsolidatedConfig::class); + $this->objectManager->addSharedInstance( + $this->consolidatedMock, + \Magento\Integration\Model\ConsolidatedConfig::class + ); + $this->integrationManager = $this->objectManager->create( + \Magento\Integration\Model\ConfigBasedIntegrationManager::class, + [] + ); + $this->integrationService = $this->objectManager->create( + \Magento\Integration\Api\IntegrationServiceInterface::class, + [] + ); + } + + /** + * @inheritdoc + */ + protected function tearDown() + { + $this->objectManager->removeSharedInstance(\Magento\Integration\Model\ConsolidatedConfig::class); + parent::tearDown(); + } + + /** + * @magentoDbIsolation enabled + */ + public function testProcessConfigBasedIntegrations() + { + $newIntegrations = require __DIR__ . '/Config/Consolidated/_files/integration.php'; + $this->consolidatedMock + ->expects($this->any()) + ->method('getIntegrations') + ->willReturn($newIntegrations); + + // Check that the integrations do not exist already + foreach ($newIntegrations as $integrationName => $integrationData) { + $integration = $this->integrationService->findByName($integrationName); + $this->assertEquals(null, $integration->getId(), 'Integration already exists'); + } + + // Create new integrations + $this->assertEquals( + $newIntegrations, + $this->integrationManager->processConfigBasedIntegrations($newIntegrations), + 'Error processing config based integrations.' + ); + $createdIntegrations = []; + + // Check that the integrations are new with "inactive" status + foreach ($newIntegrations as $integrationName => $integrationData) { + $integration = $this->integrationService->findByName($integrationName); + $this->assertNotEmpty($integration->getId(), 'Integration was not created'); + $this->assertEquals( + $integration::STATUS_INACTIVE, + $integration->getStatus(), + 'Integration is not created with "inactive" status' + ); + $createdIntegrations[$integrationName] = $integration; + } + + // Rerun integration creation with the same data (data has not changed) + $this->assertEquals( + $newIntegrations, + $this->integrationManager->processConfigBasedIntegrations($newIntegrations), + 'Error processing config based integrations.' + ); + + // Check that the integrations are not recreated when data has not actually changed + foreach ($newIntegrations as $integrationName => $integrationData) { + $integration = $this->integrationService->findByName($integrationName); + $this->assertEquals( + $createdIntegrations[$integrationName]->getId(), + $integration->getId(), + 'Integration ID has changed' + ); + $this->assertEquals( + $createdIntegrations[$integrationName]->getStatus(), + $integration->getStatus(), + 'Integration status has changed' + ); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/NewRelicReporting/Plugin/SeparateAppsTest.php b/dev/tests/integration/testsuite/Magento/NewRelicReporting/Plugin/SeparateAppsTest.php index 92b0ec0f6a732..9271e08942279 100644 --- a/dev/tests/integration/testsuite/Magento/NewRelicReporting/Plugin/SeparateAppsTest.php +++ b/dev/tests/integration/testsuite/Magento/NewRelicReporting/Plugin/SeparateAppsTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\NewRelicReporting\Plugin; use Magento\Framework\App\State; @@ -10,6 +12,9 @@ use Magento\TestFramework\ObjectManager; use Magento\TestFramework\Helper\Bootstrap; +/** + * Class SeparateAppsTest + */ class SeparateAppsTest extends \PHPUnit\Framework\TestCase { /** @@ -17,6 +22,9 @@ class SeparateAppsTest extends \PHPUnit\Framework\TestCase */ private $objectManager; + /** + * @inheritdoc + */ protected function setUp() { $this->objectManager = Bootstrap::getObjectManager(); @@ -44,4 +52,25 @@ public function testAppNameIsSetWhenConfiguredCorrectly() $state->setAreaCode('90210'); } + + /** + * @magentoConfigFixture default/newrelicreporting/general/enable 1 + * @magentoConfigFixture default/newrelicreporting/general/app_name beverly_hills + * @magentoConfigFixture default/newrelicreporting/general/separate_apps 0 + */ + public function testAppNameIsNotSetWhenDisabled() + { + $newRelicWrapper = $this->getMockBuilder(NewRelicWrapper::class) + ->setMethods(['setAppName']) + ->getMock(); + + $this->objectManager->configure([NewRelicWrapper::class => ['shared' => true]]); + $this->objectManager->addSharedInstance($newRelicWrapper, NewRelicWrapper::class); + + $newRelicWrapper->expects($this->never())->method('setAppName'); + + $state = $this->objectManager->get(State::class); + + $state->setAreaCode('90210'); + } } diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php index 6e356694b2f03..c6ba498319ff8 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php @@ -33,7 +33,7 @@ public function testEmailConfirmation() $this->assertContains( '/newsletter/subscriber/confirm/id/' . $this->_model->getSubscriberId() . '/code/ysayquyajua23iq29gxwu2eax2qb6gvy', - $transportBuilder->getSentMessage()->getBodyHtml()->getRawContent() + $transportBuilder->getSentMessage()->getRawMessage() ); $this->assertEquals(Subscriber::STATUS_NOT_ACTIVE, $this->_model->getSubscriberStatus()); } diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_second_website.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_second_website.php new file mode 100644 index 0000000000000..52551e6dc96d8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_second_website.php @@ -0,0 +1,40 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\App\ResourceConnection; +use Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\TestFramework\Helper\Bootstrap; + +$objectManager = Bootstrap::getObjectManager(); + +/** @var WebsiteRepositoryInterface $websiteRepository */ +$websiteRepository = $objectManager->get(WebsiteRepositoryInterface::class); +$website = $websiteRepository->get('test'); + +/** @var ResourceConnection $resource */ +$resource = $objectManager->get(ResourceConnection::class); +$connection = $resource->getConnection(); +$resourceModel = $objectManager->create(Tablerate::class); +$entityTable = $resourceModel->getTable('shipping_tablerate'); +$data = + [ + 'website_id' => $website->getId(), + 'dest_country_id' => 'US', + 'dest_region_id' => 0, + 'dest_zip' => '*', + 'condition_name' => 'package_qty', + 'condition_value' => 1, + 'price' => 20, + 'cost' => 20 + ]; +$connection->query( + "INSERT INTO {$entityTable} (`website_id`, `dest_country_id`, `dest_region_id`, `dest_zip`, `condition_name`," + . "`condition_value`, `price`, `cost`) VALUES (:website_id, :dest_country_id, :dest_region_id, :dest_zip," + . " :condition_name, :condition_value, :price, :cost);", + $data +); diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_second_website_rollback.php b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_second_website_rollback.php new file mode 100644 index 0000000000000..9606b0eb605fd --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/tablerates_second_website_rollback.php @@ -0,0 +1,13 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); +$resource = $objectManager->get(\Magento\Framework\App\ResourceConnection::class); +$connection = $resource->getConnection(); +$resourceModel = $objectManager->create(\Magento\OfflineShipping\Model\ResourceModel\Carrier\Tablerate::class); +$entityTable = $resourceModel->getTable('shipping_tablerate'); +$connection->query("DELETE FROM {$entityTable};"); diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php index 412ecd80847c1..eb80da6d21b19 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Express/CheckoutTest.php @@ -293,7 +293,9 @@ public function testReturnFromPaypal() /** * The case when handling address data from Paypal button. * System's address fields are replacing from export Paypal data. + * Billing and Shipping address are the same. * + * @return void * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php * @magentoAppIsolation enabled * @magentoDbIsolation enabled @@ -307,39 +309,96 @@ public function testReturnFromPaypalButton() $this->checkoutModel->returnFromPaypal('token'); $shippingAddress = $quote->getShippingAddress(); - - $prefix = ''; - $this->assertEquals([$prefix . $this->getExportedData()['street']], $shippingAddress->getStreet()); - $this->assertEquals($prefix . $this->getExportedData()['firstname'], $shippingAddress->getFirstname()); - $this->assertEquals($prefix . $this->getExportedData()['city'], $shippingAddress->getCity()); - $this->assertEquals($prefix . $this->getExportedData()['telephone'], $shippingAddress->getTelephone()); - $this->assertEquals($prefix . $this->getExportedData()['email'], $shippingAddress->getEmail()); + $billingAddress = $quote->getBillingAddress(); + $exportedShippingData = $this->getExportedData()['shipping']; + + $this->assertEquals([$exportedShippingData['street']], $shippingAddress->getStreet()); + $this->assertEquals($exportedShippingData['firstname'], $shippingAddress->getFirstname()); + $this->assertEquals($exportedShippingData['city'], $shippingAddress->getCity()); + $this->assertEquals($exportedShippingData['telephone'], $shippingAddress->getTelephone()); + $this->assertEquals($exportedShippingData['email'], $shippingAddress->getEmail()); + + $this->assertEquals([$exportedShippingData['street']], $billingAddress->getStreet()); + $this->assertEquals($exportedShippingData['firstname'], $billingAddress->getFirstname()); + $this->assertEquals($exportedShippingData['city'], $billingAddress->getCity()); + $this->assertEquals($exportedShippingData['telephone'], $billingAddress->getTelephone()); + $this->assertEquals($exportedShippingData['email'], $billingAddress->getEmail()); } /** * The case when handling address data from the checkout. * System's address fields are not replacing from export PayPal data. + * Billing and Shipping address are the same * + * @return void * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php * @magentoAppIsolation enabled * @magentoDbIsolation enabled */ public function testReturnFromPaypalIfCheckout() { + $prefix = 'exported'; $quote = $this->getFixtureQuote(); - $this->prepareCheckoutModel($quote); + $this->prepareCheckoutModel($quote, $prefix); $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0); + $originalShippingAddress = $quote->getShippingAddress(); + $originalBillingAddress = $quote->getBillingAddress(); + $this->checkoutModel->returnFromPaypal('token'); $shippingAddress = $quote->getShippingAddress(); + $billingAddress = $quote->getBillingAddress(); + + $this->assertEquals($originalShippingAddress->getStreet(), $shippingAddress->getStreet()); + $this->assertEquals($originalShippingAddress->getFirstname(), $shippingAddress->getFirstname()); + $this->assertEquals($originalShippingAddress->getCity(), $shippingAddress->getCity()); + $this->assertEquals($originalShippingAddress->getTelephone(), $shippingAddress->getTelephone()); + $this->assertEquals($originalBillingAddress->getStreet(), $billingAddress->getStreet()); + $this->assertEquals($originalBillingAddress->getFirstname(), $billingAddress->getFirstname()); + $this->assertEquals($originalBillingAddress->getCity(), $billingAddress->getCity()); + $this->assertEquals($originalBillingAddress->getTelephone(), $billingAddress->getTelephone()); + } + + /** + * The case when handling address data from the checkout. + * System's address fields are replacing billing address from export PayPal data. + * Billing and Shipping address are different + * + * @return void + * @magentoDataFixture Magento/Paypal/_files/quote_payment_express_with_customer.php + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + */ + public function testReturnFromPaypalIfCheckoutWithReturnBillingAddress() + { $prefix = 'exported'; + $quote = $this->getFixtureQuote(); + $this->paypalConfig->expects($this->exactly(2)) + ->method('getValue') + ->with('requireBillingAddress') + ->willReturn(1); + $this->prepareCheckoutModel($quote, $prefix); + $quote->getPayment()->setAdditionalInformation(Checkout::PAYMENT_INFO_BUTTON, 0); + + $originalShippingAddress = $quote->getShippingAddress(); + + $this->checkoutModel->returnFromPaypal('token'); - $this->assertNotEquals([$prefix . $this->getExportedData()['street']], $shippingAddress->getStreet()); - $this->assertNotEquals($prefix . $this->getExportedData()['firstname'], $shippingAddress->getFirstname()); - $this->assertNotEquals($prefix . $this->getExportedData()['city'], $shippingAddress->getCity()); - $this->assertNotEquals($prefix . $this->getExportedData()['telephone'], $shippingAddress->getTelephone()); + $shippingAddress = $quote->getShippingAddress(); + $billingAddress = $quote->getBillingAddress(); + $exportedBillingData = $this->getExportedData()['billing']; + + $this->assertEquals($originalShippingAddress->getStreet(), $shippingAddress->getStreet()); + $this->assertEquals($originalShippingAddress->getFirstname(), $shippingAddress->getFirstname()); + $this->assertEquals($originalShippingAddress->getCity(), $shippingAddress->getCity()); + $this->assertEquals($originalShippingAddress->getTelephone(), $shippingAddress->getTelephone()); + + $this->assertEquals([$prefix . $exportedBillingData['street']], $billingAddress->getStreet()); + $this->assertEquals($prefix . $exportedBillingData['firstname'], $billingAddress->getFirstname()); + $this->assertEquals($prefix . $exportedBillingData['city'], $billingAddress->getCity()); + $this->assertEquals($prefix . $exportedBillingData['telephone'], $billingAddress->getTelephone()); } /** @@ -347,6 +406,7 @@ public function testReturnFromPaypalIfCheckout() * Customer add virtual product to quote and place order using PayPal Express method. * After return from PayPal quote billing address have to be updated by PayPal Express address. * + * @return void * @magentoDataFixture Magento/Paypal/_files/virtual_quote_with_empty_billing_address.php * @magentoConfigFixture current_store payment/paypal_express/active 1 * @magentoDbIsolation enabled @@ -361,7 +421,7 @@ public function testReturnFromPaypalForCustomerWithEmptyAddresses() $billingAddress = $quote->getBillingAddress(); - $this->performQuoteAddressAssertions($billingAddress, $this->getExportedData()); + $this->performQuoteAddressAssertions($billingAddress, $this->getExportedData()['billing']); } /** @@ -430,8 +490,10 @@ private function performQuoteAddressAssertions(Address $address, array $expected * Initialize a checkout model mock. * * @param Quote $quote + * @param string $prefix + * @return void */ - private function prepareCheckoutModel(Quote $quote) + private function prepareCheckoutModel(Quote $quote, $prefix = '') { $this->checkoutModel = $this->objectManager->create( Checkout::class, @@ -442,11 +504,11 @@ private function prepareCheckoutModel(Quote $quote) ] ); - $exportedBillingAddress = $this->getExportedAddressFixture($this->getExportedData()); + $exportedBillingAddress = $this->getExportedAddressFixture($this->getExportedData()['billing'], $prefix); $this->api->method('getExportedBillingAddress') ->will($this->returnValue($exportedBillingAddress)); - $exportedShippingAddress = $this->getExportedAddressFixture($this->getExportedData()); + $exportedShippingAddress = $this->getExportedAddressFixture($this->getExportedData()['shipping'], $prefix); $this->api->method('getExportedShippingAddress') ->will($this->returnValue($exportedShippingAddress)); @@ -459,19 +521,33 @@ private function prepareCheckoutModel(Quote $quote) * * @return array */ - private function getExportedData() + private function getExportedData(): array { return [ - 'email' => 'customer@example.com', - 'firstname' => 'John', - 'lastname' => 'Doe', - 'country' => 'US', - 'region' => 'Colorado', - 'region_id' => '13', - 'city' => 'Denver', - 'street' => '66 Pearl St', - 'postcode' => '80203', - 'telephone' => '555-555-555', + 'shipping' => [ + 'email' => 'customer@example.com', + 'firstname' => 'John', + 'lastname' => 'Doe', + 'country' => 'US', + 'region' => 'Colorado', + 'region_id' => '13', + 'city' => 'Denver', + 'street' => '66 Pearl St', + 'postcode' => '80203', + 'telephone' => '555-555-555', + ], + 'billing' => [ + 'email' => 'customer@example.com', + 'firstname' => 'Jane', + 'lastname' => 'Doe', + 'country' => 'US', + 'region' => 'Texas', + 'region_id' => '13', + 'city' => 'Austin', + 'street' => '1100 Congress Ave', + 'postcode' => '78701', + 'telephone' => '555-555-555', + ] ]; } diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php index b545bb8a9f742..aed9214899237 100644 --- a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php +++ b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/EmailTest.php @@ -82,7 +82,7 @@ public function testSend($isCustomerIdUsed) ); $this->assertContains( 'John Smith,', - $transportBuilder->getSentMessage()->getBodyHtml()->getRawContent() + $transportBuilder->getSentMessage()->getRawMessage() ); } diff --git a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php index 50213710233d2..0fc98d8d8380b 100644 --- a/dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php +++ b/dev/tests/integration/testsuite/Magento/ProductAlert/Model/ObserverTest.php @@ -56,7 +56,7 @@ public function testProcess() ); $this->assertContains( 'John Smith,', - $transportBuilder->getSentMessage()->getBodyHtml()->getRawContent() + $transportBuilder->getSentMessage()->getRawMessage() ); } } diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteValidatorTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteValidatorTest.php index d3524830b93d4..1088d0f4fbeb0 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteValidatorTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteValidatorTest.php @@ -9,6 +9,9 @@ use Magento\TestFramework\Helper\Bootstrap; +/** + * @magentoDbIsolation disabled + */ class QuoteValidatorTest extends \PHPUnit\Framework\TestCase { /** 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 e071dde26a263..7110f39ee532c 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 @@ -6,12 +6,26 @@ namespace Magento\Sales\Controller\Adminhtml\Order; use Magento\Customer\Api\CustomerRepositoryInterface; -use Magento\Backend\Model\Session\Quote; +use Magento\Backend\Model\Session\Quote as SessionQuote; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\App\Config\MutableScopeConfigInterface; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Quote\Api\CartRepositoryInterface; +use Magento\Framework\App\Request\Http as HttpRequest; +use Magento\Quote\Model\Quote; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Api\Data\WebsiteInterface; +use Magento\Store\Api\StoreRepositoryInterface; +use Magento\Store\Api\WebsiteRepositoryInterface; +use Magento\Store\Model\ScopeInterface; /** * @magentoAppArea adminhtml * @magentoDbIsolation enabled + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyPublicMethods) */ class CreateTest extends \Magento\TestFramework\TestCase\AbstractBackendController { @@ -57,6 +71,66 @@ public function testLoadBlockActionData() } /** + * Tests that shipping method 'Table rates' shows rates according to selected website. + * + * @magentoAppArea adminhtml + * @magentoDataFixture Magento/Quote/Fixtures/quote_sec_website.php + * @magentoDataFixture Magento/OfflineShipping/_files/tablerates_second_website.php + * @magentoDbIsolation disabled + */ + public function testLoadBlockShippingMethod() + { + $store = $this->getStore('fixture_second_store'); + + /** @var MutableScopeConfigInterface $mutableScopeConfig */ + $mutableScopeConfig = $this->_objectManager->get(MutableScopeConfigInterface::class); + $mutableScopeConfig->setValue( + 'carriers/tablerate/active', + 1, + ScopeInterface::SCOPE_STORE, + $store->getCode() + ); + $mutableScopeConfig->setValue( + 'carriers/tablerate/condition_name', + 'package_qty', + ScopeInterface::SCOPE_STORE, + $store->getCode() + ); + $website = $this->getWebsite('test'); + $customer = $this->getCustomer('customer.web@example.com', (int)$website->getId()); + $quote = $this->getQuoteById('0000032134'); + $session = $this->_objectManager->get(SessionQuote::class); + $session->setQuoteId($quote->getId()); + + $data = [ + 'firstname' => 'John', + 'lastname' => 'Doe', + 'street' => ['Soborna 23'], + 'city' => 'Testcity', + 'country_id' => 'US', + 'region' => 'Alabama', + 'region_id' => 1 + ]; + $this->getRequest()->setPostValue( + [ + 'order' => ['billing_address' => $data], + 'reset_shipping' => 1, + 'collect_shipping_rates' => 1, + 'customer_id' => $customer->getId(), + 'store_id' => $store->getId(), + 'json' => true + ] + ); + $this->dispatch('backend/sales/order_create/loadBlock/block/shipping_method'); + $body = $this->getResponse()->getBody(); + $expectedTableRatePrice = '<span class=\"price\">$20.00<\/span>'; + + $this->assertContains($expectedTableRatePrice, $body, ''); + } + + /** + * Tests LoadBlock actions. + * * @dataProvider loadBlockActionsDataProvider */ public function testLoadBlockActions($block, $expected) @@ -80,6 +154,8 @@ public function loadBlockActionsDataProvider() } /** + * Tests action items. + * * @magentoDataFixture Magento/Catalog/_files/product_simple.php */ public function testLoadBlockActionItems() @@ -153,6 +229,8 @@ public function testIndexAction() } /** + * Tests ACL. + * * @param string $actionName * @param boolean $reordered * @param string $expectedResult @@ -162,7 +240,7 @@ public function testIndexAction() */ public function testGetAclResource($actionName, $reordered, $expectedResult) { - $this->_objectManager->get(Quote::class)->setReordered($reordered); + $this->_objectManager->get(SessionQuote::class)->setReordered($reordered); $orderController = $this->_objectManager->get( \Magento\Sales\Controller\Adminhtml\Order\Stub\OrderCreateStub::class ); @@ -251,7 +329,7 @@ public function testSyncBetweenQuoteAddresses() $quoteRepository = $this->_objectManager->get(CartRepositoryInterface::class); $quote = $quoteRepository->getActiveForCustomer($customer->getId()); - $session = $this->_objectManager->get(Quote::class); + $session = $this->_objectManager->get(SessionQuote::class); $session->setQuoteId($quote->getId()); $data = [ @@ -286,4 +364,69 @@ public function testSyncBetweenQuoteAddresses() self::assertEquals($data['city'], $shippingAddress->getCity()); self::assertEquals($data['street'], $shippingAddress->getStreet()); } + + /** + * Gets quote entity by reserved order id. + * + * @param string $reservedOrderId + * @return Quote + */ + private function getQuoteById(string $reservedOrderId): Quote + { + /** @var SearchCriteriaBuilder $searchCriteriaBuilder */ + $searchCriteriaBuilder = $this->_objectManager->get(SearchCriteriaBuilder::class); + $searchCriteria = $searchCriteriaBuilder->addFilter('reserved_order_id', $reservedOrderId) + ->create(); + + /** @var CartRepositoryInterface $repository */ + $repository = $this->_objectManager->get(CartRepositoryInterface::class); + $items = $repository->getList($searchCriteria) + ->getItems(); + + return array_pop($items); + } + + /** + * Gets website entity. + * + * @param string $code + * @return WebsiteInterface + * @throws NoSuchEntityException + */ + private function getWebsite(string $code): WebsiteInterface + { + /** @var WebsiteRepositoryInterface $repository */ + $repository = $this->_objectManager->get(WebsiteRepositoryInterface::class); + return $repository->get($code); + } + + /** + * Gets customer entity. + * + * @param string $email + * @param int $websiteId + * @return CustomerInterface + * @throws NoSuchEntityException + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getCustomer(string $email, int $websiteId): CustomerInterface + { + /** @var CustomerRepositoryInterface $repository */ + $repository = $this->_objectManager->get(CustomerRepositoryInterface::class); + return $repository->get($email, $websiteId); + } + + /** + * Gets store by code. + * + * @param string $code + * @return StoreInterface + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function getStore(string $code): StoreInterface + { + /** @var StoreRepositoryInterface $repository */ + $repository = $this->_objectManager->get(StoreRepositoryInterface::class); + return $repository->get($code); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Address/RendererTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Address/RendererTest.php index 1bad0eec7d1d0..1df0864e1874d 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Address/RendererTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/Order/Address/RendererTest.php @@ -46,7 +46,7 @@ protected function setUp() /** * @magentoDataFixture Magento/Sales/_files/order_fixture_store.php - * @magentoDbIsolation enabled + * @magentoDbIsolation disabled * @magentoAppIsolation enabled */ public function testFormat() diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php new file mode 100644 index 0000000000000..8eda87ae43c29 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Sales\Model\ResourceModel\Order\Grid; + +use Magento\TestFramework\Helper\Bootstrap; + +class CollectionTest extends \PHPUnit\Framework\TestCase +{ + /** + * Tests collection properties. + * + * @throws \ReflectionException + * @return void + */ + public function testCollectionCreate() + { + $objectManager = Bootstrap::getObjectManager(); + + /** @var Collection $gridCollection */ + $gridCollection = $objectManager->get(Collection::class); + $tableDescription = $gridCollection->getConnection() + ->describeTable($gridCollection->getMainTable()); + + $mapper = new \ReflectionMethod( + Collection::class, + '_getMapper' + ); + $mapper->setAccessible(true); + $map = $mapper->invoke($gridCollection); + + self::assertInternalType('array', $map); + self::assertArrayHasKey('fields', $map); + self::assertInternalType('array', $map['fields']); + self::assertCount(count($tableDescription), $map['fields']); + + foreach ($map['fields'] as $mappedName) { + self::assertContains('main_table.', $mappedName); + } + } +} diff --git a/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_list.php b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_list.php index f66448460c884..742656a1ae369 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_list.php +++ b/dev/tests/integration/testsuite/Magento/Sales/_files/shipment_list.php @@ -19,6 +19,7 @@ 'shipping_address_id' => 1, 'shipment_status' => \Magento\Sales\Model\Order\Shipment::STATUS_NEW, 'store_id' => 1, + 'shipping_label' => 'shipping_label_100000001', ], [ 'increment_id' => '100000002', @@ -26,6 +27,7 @@ 'shipping_address_id' => 3, 'shipment_status' => \Magento\Sales\Model\Order\Shipment::STATUS_NEW, 'store_id' => 1, + 'shipping_label' => 'shipping_label_100000002', ], [ 'increment_id' => '100000003', @@ -33,6 +35,7 @@ 'shipping_address_id' => 3, 'status' => \Magento\Sales\Model\Order\Shipment::STATUS_NEW, 'store_id' => 1, + 'shipping_label' => 'shipping_label_100000003', ], [ 'increment_id' => '100000004', @@ -40,6 +43,7 @@ 'shipping_address_id' => 4, 'shipment_status' => 'closed', 'store_id' => 1, + 'shipping_label' => 'shipping_label_100000004', ], ]; diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/PriceIndexerDimensionsModeSetCommandTest.php b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/PriceIndexerDimensionsModeSetCommandTest.php index f8c65117976eb..b1407f4266c13 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/PriceIndexerDimensionsModeSetCommandTest.php +++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/PriceIndexerDimensionsModeSetCommandTest.php @@ -10,19 +10,16 @@ use Magento\Framework\Console\Cli; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; -use Magento\Catalog\Console\Command\PriceIndexerDimensionsModeSetCommand; -use Magento\Catalog\Model\Indexer\Product\Price\ModeSwitcher; /** - * Class PriceIndexerDimensionsModeSetCommand - * @package Magento\Setup\Console\Command + * Test command that sets indexer mode for catalog_product_price indexer */ class PriceIndexerDimensionsModeSetCommandTest extends \Magento\TestFramework\Indexer\TestCase { /** @var ObjectManagerInterface */ private $objectManager; - /** @var GenerateFixturesCommand */ + /** @var \Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand */ private $command; /** @var CommandTester */ @@ -38,7 +35,7 @@ public function setUp() $this->objectManager->get(\Magento\TestFramework\App\Config::class)->clean(); $this->command = $this->objectManager->create( - \Magento\Catalog\Console\Command\PriceIndexerDimensionsModeSetCommand::class + \Magento\Indexer\Console\Command\IndexerSetDimensionsModeCommand::class ); $this->commandTester = new CommandTester($this->command); @@ -46,14 +43,6 @@ public function setUp() parent::setUp(); } - /** - * tearDown - */ - public function tearDown() - { - parent::tearDown(); - } - /** * setUpBeforeClass */ @@ -82,11 +71,12 @@ public function testSwitchMode($previousMode, $currentMode) { $this->commandTester->execute( [ - PriceIndexerDimensionsModeSetCommand::INPUT_KEY_MODE => $currentMode + 'indexer' => 'catalog_product_price', + 'mode' => $currentMode, ] ); - $expectedOutput = 'Dimensions mode for indexer Product Price was changed from \'' - . $previousMode . '\' to \'' . $currentMode . '\''; + $expectedOutput = 'Dimensions mode for indexer "Product Price" was changed from \'' + . $previousMode . '\' to \'' . $currentMode . '\'' . PHP_EOL; $actualOutput = $this->commandTester->getDisplay(); @@ -133,10 +123,11 @@ public function testSwitchModeForSameMode() { $this->commandTester->execute( [ - PriceIndexerDimensionsModeSetCommand::INPUT_KEY_MODE => DimensionModeConfiguration::DIMENSION_NONE + 'indexer' => 'catalog_product_price', + 'mode' => DimensionModeConfiguration::DIMENSION_NONE ] ); - $expectedOutput = 'Dimensions mode for indexer Product Price has not been changed'; + $expectedOutput = 'Dimensions mode for indexer "Product Price" has not been changed' . PHP_EOL; $actualOutput = $this->commandTester->getDisplay(); @@ -159,8 +150,7 @@ public function testSwitchModeWithInvalidArgument() { $this->commandTester->execute( [ - PriceIndexerDimensionsModeSetCommand::INPUT_KEY_MODE => DimensionModeConfiguration::DIMENSION_NONE . - '_not_valid' + 'indexer' => 'indexer_not_valid' ] ); } diff --git a/dev/tests/integration/testsuite/Magento/Store/_files/websites_different_countries.php b/dev/tests/integration/testsuite/Magento/Store/_files/websites_different_countries.php index 6a00031434b3d..dd58f4eb7a984 100644 --- a/dev/tests/integration/testsuite/Magento/Store/_files/websites_different_countries.php +++ b/dev/tests/integration/testsuite/Magento/Store/_files/websites_different_countries.php @@ -10,6 +10,7 @@ use Magento\Store\Model\Store; use Magento\CatalogSearch\Model\Indexer\Fulltext as FulltextIndex; use Magento\Framework\App\Config\ReinitableConfigInterface; +use Magento\Store\Model\Group; $objectManager = Bootstrap::getObjectManager(); //Creating second website with a store. @@ -21,12 +22,23 @@ $website->setData([ 'code' => 'test', 'name' => 'Test Website', - 'default_group_id' => '1', 'is_default' => '0', ]); $website->save(); } +/** + * @var Group $storeGroup + */ +$storeGroup = $objectManager->create(Group::class); +$storeGroup->setCode('some_group') + ->setName('custom store group') + ->setWebsite($website); +$storeGroup->save($storeGroup); + +$website->setDefaultGroupId($storeGroup->getId()); +$website->save($website); + $websiteId = $website->getId(); $store = $objectManager->create(Store::class); $store->load('fixture_second_store', 'code'); diff --git a/dev/tests/integration/testsuite/Magento/Store/_files/websites_different_countries_rollback.php b/dev/tests/integration/testsuite/Magento/Store/_files/websites_different_countries_rollback.php index 0994655622cbb..8cfbe51d0668f 100644 --- a/dev/tests/integration/testsuite/Magento/Store/_files/websites_different_countries_rollback.php +++ b/dev/tests/integration/testsuite/Magento/Store/_files/websites_different_countries_rollback.php @@ -13,18 +13,18 @@ use Magento\Framework\App\Config\ReinitableConfigInterface; $objectManager = Bootstrap::getObjectManager(); -//Deleting second website's store. -$store = $objectManager->create(Store::class); -if ($store->load('fixture_second_store', 'code')->getId()) { - $store->delete(); -} -//Deleting the second website. /** @var Registry $registry */ $registry = $objectManager->get(Registry::class); $registry->unregister('isSecureArea'); $registry->register('isSecureArea', true); +//Deleting second website's store. +$store = $objectManager->create(Store::class); +if ($store->load('fixture_second_store', 'code')->getId()) { + $store->delete(); +} + $configResource = $objectManager->get(\Magento\Config\Model\ResourceModel\Config::class); //Restoring allowed countries. $configResource->deleteConfig( diff --git a/dev/tests/integration/testsuite/Magento/Swatches/Observer/AddSwatchAttributeTypeObserverTest.php b/dev/tests/integration/testsuite/Magento/Swatches/Observer/AddSwatchAttributeTypeObserverTest.php new file mode 100644 index 0000000000000..15d10c2ddffd4 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Swatches/Observer/AddSwatchAttributeTypeObserverTest.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Swatches\Observer; + +use Magento\Framework\DataObject; +use Magento\Swatches\Model\Swatch; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Framework\Event\ManagerInterface; + +/** + * Test checks that swatch types are added to the other attribute types + */ +class AddSwatchAttributeTypeObserverTest extends \PHPUnit\Framework\TestCase +{ + /** + * @magentoAppArea adminhtml + */ + public function testAddSwatchAttributeTypes() + { + $objectManager = Bootstrap::getObjectManager(); + $eventManager = $objectManager->get(ManagerInterface::class); + $response = new DataObject(); + $response->setTypes([]); + + $eventManager->dispatch( + 'adminhtml_product_attribute_types', + ['response' => $response] + ); + + $responseTypes = $response->getTypes(); + + self::assertGreaterThan(0, count($responseTypes)); + + /* Iterate through values since other types (not swatches) might be added by observers */ + $responseTypeValues = []; + foreach ($responseTypes as $responseType) { + $responseTypeValues[] = $responseType['value']; + } + + self::assertTrue(in_array(Swatch::SWATCH_TYPE_VISUAL_ATTRIBUTE_FRONTEND_INPUT, $responseTypeValues)); + self::assertTrue(in_array(Swatch::SWATCH_TYPE_TEXTUAL_ATTRIBUTE_FRONTEND_INPUT, $responseTypeValues)); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php index 4999e379e1d0a..92eae7a3fe3d7 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php @@ -170,9 +170,7 @@ public function testSendAction() \Magento\TestFramework\Mail\Template\TransportBuilderMock::class ); - $actualResult = \Zend_Mime_Decode::decodeQuotedPrintable( - $transportBuilder->getSentMessage()->getBodyHtml()->getContent() - ); + $actualResult = quoted_printable_decode($transportBuilder->getSentMessage()->getRawMessage()); $this->assertStringMatchesFormat( '%A' . $this->_customerViewHelper->getCustomerName($this->_customerSession->getCustomerDataObject()) diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist.php index 4c183ad6368cc..73ced4d5462cc 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/wishlist.php @@ -12,14 +12,4 @@ ); $wishlist->loadByCustomerId($customer->getId(), true); $item = $wishlist->addNewItem($product, new \Magento\Framework\DataObject([])); -// 'product' => '1', -// 'related_product' => '', -// 'options' => array( -// 1 => '1-text', -// 2 => array('month' => 1, 'day' => 1, 'year' => 2001, 'hour' => 1, 'minute' => 1), -// 3 => '1', -// 4 => '1', -// ), -// 'validate_datetime_2' => '', -// 'qty' => '1', $wishlist->setSharingCode('fixture_unique_code')->save(); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js index 52739eec2782b..df6996afeb965 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/cc-form.test.js @@ -19,7 +19,12 @@ define([ billingAddress: ko.observable(), shippingAddress: ko.observable(), paymentMethod: ko.observable(), - totals: ko.observable({}) + totals: ko.observable({}), + + /** Stub */ + isVirtual: function () { + return false; + } }, 'Magento_Braintree/js/view/payment/validator-handler': jasmine.createSpyObj( 'validator-handler', diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js index bcfaecd277754..a2373cfb99091 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Braintree/frontend/js/view/payment/method-renderer/paypal.test.js @@ -24,7 +24,12 @@ define([ paymentMethod: ko.observable(), totals: ko.observable({ 'base_grand_total': 0 - }) + }), + + /** Stub */ + isVirtual: function () { + return false; + } }, 'Magento_Braintree/js/view/payment/adapter': { config: {}, diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js index 216ad28cf496a..ec1bf30fc4e8e 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Checkout/frontend/js/model/cart/estimate-service.test.js @@ -143,5 +143,10 @@ define([ }); expect(mocks['Magento_Checkout/js/model/cart/totals-processor/default'].estimateTotals).toHaveBeenCalled(); }); + + it('test subscribe when cart data was changed', function () { + mocks['Magento_Customer/js/customer-data'].get('cart')({ data_id: 2 }); + expect(mocks['Magento_Checkout/js/model/cart/totals-processor/default'].estimateTotals).toHaveBeenCalled(); + }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js index 47e3507ea1321..12e12eb492c89 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Paypal/frontend/js/view/payment/method-renderer/paypal-express-abstract.test.js @@ -28,8 +28,12 @@ define([ billingAddress: ko.observable(), shippingAddress: ko.observable(), paymentMethod: ko.observable(), - totals: ko.observable({}) + totals: ko.observable({}), + /** Stub */ + isVirtual: function () { + return false; + } }, 'Magento_Checkout/js/action/set-payment-information': setPaymentMock, 'Magento_Checkout/js/model/payment/additional-validators': { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/sticky/sticky.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/sticky/sticky.test.js index 40bfad0519ec6..9a6c28757e89f 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/sticky/sticky.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/sticky/sticky.test.js @@ -46,8 +46,10 @@ define([ expect(stickyObj.initListeners).toHaveBeenCalled(); }); it('has initOnScroll method', function () { + spyOn(document, 'addEventListener'); stickyObj.initOnScroll(); expect(stickyObj.lastHorizontalScrollPos).toBeDefined(); + expect(document.addEventListener).toHaveBeenCalledWith('scroll', jasmine.any(Function)); }); it('has initOnListingScroll method', function () { spyOn(stickyObj, 'initOnListingScroll'); diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php index 81088522bccb2..a4baacbe4d169 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Library/DependencyTest.php @@ -96,35 +96,6 @@ function ($file) { ); } - public function testAppCodeUsage() - { - $files = Files::init(); - $componentRegistrar = new ComponentRegistrar(); - $libPaths = $componentRegistrar->getPaths(ComponentRegistrar::LIBRARY); - $invoker = new AggregateInvoker($this); - $invoker( - function ($file) use ($libPaths) { - $content = file_get_contents($file); - foreach ($libPaths as $libPath) { - if (strpos($file, $libPath) === 0) { - $this->assertSame( - 0, - preg_match('~(?<![a-z\\d_:]|->|function\\s)__\\s*\\(~iS', $content), - 'Function __() is defined outside of the library and must not be used there. ' . - 'Replacement suggestion: new \\Magento\\Framework\\Phrase()' - ); - } - } - }, - $files->getPhpFiles( - Files::INCLUDE_PUB_CODE | - Files::INCLUDE_LIBS | - Files::AS_DATA_SET | - Files::INCLUDE_NON_CLASSES - ) - ); - } - /** * @inheritdoc */ diff --git a/dev/travis/before_script.sh b/dev/travis/before_script.sh index 7cf55ca8083f1..b49d9acf4a701 100755 --- a/dev/travis/before_script.sh +++ b/dev/travis/before_script.sh @@ -72,7 +72,7 @@ case $TEST_SUITE in --base-path="$TRAVIS_BUILD_DIR" \ --repo='https://github.com/magento/magento2.git' \ --branch="$TRAVIS_BRANCH" - cat "$changed_files_ce" | sed 's/^/ + including /' + sed 's/^/ + including /' "$changed_files_ce" cd ../../.. ;; @@ -129,6 +129,7 @@ case $TEST_SUITE in sed -e "s?basic?travis_acceptance?g" --in-place ./phpunit.xml cp ./.htaccess.sample ./.htaccess cd ./utils + php -f generate/moduleSequence.php php -f mtf troubleshooting:check-all cd ../../.. diff --git a/lib/internal/Magento/Framework/Api/SimpleDataObjectConverter.php b/lib/internal/Magento/Framework/Api/SimpleDataObjectConverter.php index a1f5acb4e60e7..49d824a4f2e5a 100644 --- a/lib/internal/Magento/Framework/Api/SimpleDataObjectConverter.php +++ b/lib/internal/Magento/Framework/Api/SimpleDataObjectConverter.php @@ -8,6 +8,9 @@ use Magento\Framework\Convert\ConvertArray; use Magento\Framework\Reflection\DataObjectProcessor; +/** + * Data object converter. + */ class SimpleDataObjectConverter { /** @@ -156,14 +159,13 @@ public static function snakeCaseToUpperCamelCase($input) */ public static function snakeCaseToCamelCase($input) { - return lcfirst(str_replace(' ', '', ucwords(str_replace('_', ' ', $input)))); + return lcfirst(self::snakeCaseToUpperCamelCase($input)); } /** * Convert a CamelCase string read from method into field key in snake_case * - * e.g. DefaultShipping => default_shipping - * Postcode => postcode + * For example [DefaultShipping => default_shipping, Postcode => postcode] * * @param string $name * @return string diff --git a/lib/internal/Magento/Framework/App/Config/ScopeCodeResolver.php b/lib/internal/Magento/Framework/App/Config/ScopeCodeResolver.php index 321997afdba95..681af35944695 100644 --- a/lib/internal/Magento/Framework/App/Config/ScopeCodeResolver.php +++ b/lib/internal/Magento/Framework/App/Config/ScopeCodeResolver.php @@ -5,6 +5,7 @@ */ namespace Magento\Framework\App\Config; +use Magento\Framework\App\ScopeInterface; use Magento\Framework\App\ScopeResolverPool; /** @@ -34,7 +35,7 @@ public function __construct(ScopeResolverPool $scopeResolverPool) * Resolve scope code * * @param string $scopeType - * @param string $scopeCode + * @param string|null $scopeCode * @return string */ public function resolve($scopeType, $scopeCode) @@ -42,20 +43,24 @@ public function resolve($scopeType, $scopeCode) if (isset($this->resolvedScopeCodes[$scopeType][$scopeCode])) { return $this->resolvedScopeCodes[$scopeType][$scopeCode]; } - if (($scopeCode === null || is_numeric($scopeCode)) - && $scopeType !== ScopeConfigInterface::SCOPE_TYPE_DEFAULT - ) { + + if ($scopeType !== ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { $scopeResolver = $this->scopeResolverPool->get($scopeType); $resolverScopeCode = $scopeResolver->getScope($scopeCode); } else { $resolverScopeCode = $scopeCode; } - if ($resolverScopeCode instanceof \Magento\Framework\App\ScopeInterface) { + if ($resolverScopeCode instanceof ScopeInterface) { $resolverScopeCode = $resolverScopeCode->getCode(); } + if ($scopeCode === null) { + $scopeCode = $resolverScopeCode; + } + $this->resolvedScopeCodes[$scopeType][$scopeCode] = $resolverScopeCode; + return $resolverScopeCode; } diff --git a/lib/internal/Magento/Framework/App/Request/DataPersistor.php b/lib/internal/Magento/Framework/App/Request/DataPersistor.php index bb3740aceb010..f0b8819621a1e 100644 --- a/lib/internal/Magento/Framework/App/Request/DataPersistor.php +++ b/lib/internal/Magento/Framework/App/Request/DataPersistor.php @@ -5,8 +5,12 @@ */ namespace Magento\Framework\App\Request; +use Magento\Framework\Api\SimpleDataObjectConverter; use Magento\Framework\Session\SessionManagerInterface; +/** + * Persist data to session. + */ class DataPersistor implements DataPersistorInterface { /** @@ -32,7 +36,7 @@ public function __construct( */ public function set($key, $data) { - $method = 'set' . ucfirst($key) . 'Data'; + $method = 'set' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key) . 'Data'; call_user_func_array([$this->session, $method], [$data]); } @@ -44,7 +48,7 @@ public function set($key, $data) */ public function get($key) { - $method = 'get' . ucfirst($key) . 'Data'; + $method = 'get' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key) . 'Data'; return call_user_func_array([$this->session, $method], []); } @@ -56,7 +60,7 @@ public function get($key) */ public function clear($key) { - $method = 'uns' . ucfirst($key) . 'Data'; + $method = 'uns' . SimpleDataObjectConverter::snakeCaseToUpperCamelCase($key) . 'Data'; call_user_func_array([$this->session, $method], []); } } diff --git a/lib/internal/Magento/Framework/Archive/Tar.php b/lib/internal/Magento/Framework/Archive/Tar.php index 7fe1255e5b859..a858b2411515a 100644 --- a/lib/internal/Magento/Framework/Archive/Tar.php +++ b/lib/internal/Magento/Framework/Archive/Tar.php @@ -4,15 +4,15 @@ * See COPYING.txt for license details. */ +namespace Magento\Framework\Archive; + +use Magento\Framework\Archive\Helper\File; + /** * Class to work with tar archives * * @author Magento Core Team <core@magentocommerce.com> */ -namespace Magento\Framework\Archive; - -use Magento\Framework\Archive\Helper\File; - class Tar extends \Magento\Framework\Archive\AbstractArchive implements \Magento\Framework\Archive\ArchiveInterface { /** @@ -259,10 +259,7 @@ protected function _createTar($skipRoot = false, $finalize = false) ); } - array_shift($dirFiles); - /* remove './'*/ - array_shift($dirFiles); - /* remove '../'*/ + $dirFiles = array_diff($dirFiles, ['..', '.']); foreach ($dirFiles as $item) { $this->_setCurrentFile($file . $item)->_createTar(); diff --git a/lib/internal/Magento/Framework/Communication/Config/Validator.php b/lib/internal/Magento/Framework/Communication/Config/Validator.php index 5e2687c3b08c1..76ef1b85b63eb 100644 --- a/lib/internal/Magento/Framework/Communication/Config/Validator.php +++ b/lib/internal/Magento/Framework/Communication/Config/Validator.php @@ -38,6 +38,8 @@ public function __construct( } /** + * Validate response schema definition for topic + * * @param string $responseSchema * @param string $topicName * @return void @@ -46,6 +48,12 @@ public function validateResponseSchemaType($responseSchema, $topicName) { try { $this->validateType($responseSchema); + } catch (\InvalidArgumentException $e) { + throw new \LogicException( + 'Response schema definition has service class with wrong annotated methods', + $e->getCode(), + $e + ); } catch (\Exception $e) { throw new \LogicException( sprintf( @@ -59,6 +67,8 @@ public function validateResponseSchemaType($responseSchema, $topicName) } /** + * Validate request schema definition for topic + * * @param string $requestSchema * @param string $topicName * @return void @@ -67,6 +77,12 @@ public function validateRequestSchemaType($requestSchema, $topicName) { try { $this->validateType($requestSchema); + } catch (\InvalidArgumentException $e) { + throw new \LogicException( + 'Request schema definition has service class with wrong annotated methods', + $e->getCode(), + $e + ); } catch (\Exception $e) { throw new \LogicException( sprintf( @@ -80,6 +96,8 @@ public function validateRequestSchemaType($requestSchema, $topicName) } /** + * Validate service method specified in the definition of handler + * * @param string $serviceName * @param string $methodName * @param string $handlerName @@ -109,6 +127,7 @@ public function validateResponseHandlersType($serviceName, $methodName, $handler * @param string $typeName * @return $this * @throws \Exception In case when type is invalid + * @throws \InvalidArgumentException if methods don't have annotation */ protected function validateType($typeName) { diff --git a/lib/internal/Magento/Framework/Composer/Remove.php b/lib/internal/Magento/Framework/Composer/Remove.php index b7a9a20333d86..b7cea7769a176 100644 --- a/lib/internal/Magento/Framework/Composer/Remove.php +++ b/lib/internal/Magento/Framework/Composer/Remove.php @@ -46,7 +46,7 @@ public function remove(array $packages) [ 'command' => 'remove', 'packages' => $packages, - '--no-update' => true, + '--no-update-with-dependencies' => true, ] ); } diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index 1449d6d2a6456..3d06e27542f07 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -3807,13 +3807,13 @@ protected function _parseTextSize($size) switch ($last) { case 'k': - $size = intval($size) * 1024; + $size = (int)$size * 1024; break; case 'm': - $size = intval($size) * 1024 * 1024; + $size = (int)$size * 1024 * 1024; break; case 'g': - $size = intval($size) * 1024 * 1024 * 1024; + $size = (int)$size * 1024 * 1024 * 1024; break; } @@ -3824,7 +3824,7 @@ protected function _parseTextSize($size) return Table::MAX_TEXT_SIZE; } - return intval($size); + return (int)$size; } /** diff --git a/lib/internal/Magento/Framework/DB/Helper.php b/lib/internal/Magento/Framework/DB/Helper.php index 4cb8304cdedf8..dc73af2a466a3 100644 --- a/lib/internal/Magento/Framework/DB/Helper.php +++ b/lib/internal/Magento/Framework/DB/Helper.php @@ -153,12 +153,12 @@ protected function _prepareHaving(\Magento\Framework\DB\Select $select, $autoRes protected function _assembleLimit($query, $limitCount, $limitOffset, $columnList = []) { if ($limitCount !== null) { - $limitCount = intval($limitCount); + $limitCount = (int)$limitCount; if ($limitCount <= 0) { //throw new \Exception("LIMIT argument count={$limitCount} is not valid"); } - $limitOffset = intval($limitOffset); + $limitOffset = (int)$limitOffset; if ($limitOffset < 0) { //throw new \Exception("LIMIT argument offset={$limitOffset} is not valid"); } diff --git a/lib/internal/Magento/Framework/DB/Query.php b/lib/internal/Magento/Framework/DB/Query.php index 04ac2006b5e1f..36aab54df3925 100644 --- a/lib/internal/Magento/Framework/DB/Query.php +++ b/lib/internal/Magento/Framework/DB/Query.php @@ -142,7 +142,7 @@ public function getSize() $sql = $this->getSelectCountSql(); $this->totalRecords = $this->getConnection()->fetchOne($sql, $this->bindParams); } - return intval($this->totalRecords); + return (int)$this->totalRecords; } /** diff --git a/lib/internal/Magento/Framework/DB/Query/BatchIterator.php b/lib/internal/Magento/Framework/DB/Query/BatchIterator.php index 7b4406fd9adb4..2758946998955 100644 --- a/lib/internal/Magento/Framework/DB/Query/BatchIterator.php +++ b/lib/internal/Magento/Framework/DB/Query/BatchIterator.php @@ -165,7 +165,7 @@ private function calculateBatchSize(Select $select) ); $row = $this->connection->fetchRow($wrapperSelect); $this->minValue = $row['max']; - return intval($row['cnt']); + return (int)$row['cnt']; } /** diff --git a/lib/internal/Magento/Framework/DB/Query/BatchRangeIterator.php b/lib/internal/Magento/Framework/DB/Query/BatchRangeIterator.php index a818d1a91f1e0..cc2f5a91f73fd 100644 --- a/lib/internal/Magento/Framework/DB/Query/BatchRangeIterator.php +++ b/lib/internal/Magento/Framework/DB/Query/BatchRangeIterator.php @@ -195,7 +195,7 @@ private function initSelectObject() ); $row = $this->connection->fetchRow($wrapperSelect); - $this->totalItemCount = intval($row['cnt']); + $this->totalItemCount = (int)$row['cnt']; } $rangeField = is_array($this->rangeField) ? $this->rangeField : [$this->rangeField]; diff --git a/lib/internal/Magento/Framework/DB/Sql/LimitExpression.php b/lib/internal/Magento/Framework/DB/Sql/LimitExpression.php index 9e875e485c67e..9b3830fcfd142 100644 --- a/lib/internal/Magento/Framework/DB/Sql/LimitExpression.php +++ b/lib/internal/Magento/Framework/DB/Sql/LimitExpression.php @@ -46,14 +46,14 @@ public function __construct( public function __toString() { $sql = $this->sql; - $count = intval($this->count); + $count = (int)$this->count; if ($count <= 0) { /** @see Zend_Db_Adapter_Exception */ #require_once 'Zend/Db/Adapter/Exception.php'; throw new \Zend_Db_Adapter_Exception("LIMIT argument count=$count is not valid"); } - $offset = intval($this->offset); + $offset = (int)$this->offset; if ($offset < 0) { /** @see Zend_Db_Adapter_Exception */ #require_once 'Zend/Db/Adapter/Exception.php'; diff --git a/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php b/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php index b6426a8d26382..9a479591bc710 100644 --- a/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php +++ b/lib/internal/Magento/Framework/Data/Argument/Interpreter/ArrayType.php @@ -90,11 +90,11 @@ private function compareItems($firstItemKey, $secondItemKey, $indexedItems) $firstValue = 0; $secondValue = 0; if (isset($firstItem['sortOrder'])) { - $firstValue = intval($firstItem['sortOrder']); + $firstValue = (int)$firstItem['sortOrder']; } if (isset($secondItem['sortOrder'])) { - $secondValue = intval($secondItem['sortOrder']); + $secondValue = (int)$secondItem['sortOrder']; } if ($firstValue == $secondValue) { diff --git a/lib/internal/Magento/Framework/Data/Collection.php b/lib/internal/Magento/Framework/Data/Collection.php index 71ec8c1aa8379..099753ac1b56f 100644 --- a/lib/internal/Magento/Framework/Data/Collection.php +++ b/lib/internal/Magento/Framework/Data/Collection.php @@ -285,7 +285,7 @@ public function getSize() if ($this->_totalRecords === null) { $this->_totalRecords = count($this->getItems()); } - return intval($this->_totalRecords); + return (int)$this->_totalRecords; } /** diff --git a/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php b/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php index b317fdb8385c7..8061b0c737bff 100644 --- a/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php +++ b/lib/internal/Magento/Framework/Data/Collection/AbstractDb.php @@ -219,7 +219,7 @@ public function getSize() $sql = $this->getSelectCountSql(); $this->_totalRecords = $this->getConnection()->fetchOne($sql, $this->_bindParams); } - return intval($this->_totalRecords); + return (int)$this->_totalRecords; } /** diff --git a/lib/internal/Magento/Framework/Exception/LocalizedException.php b/lib/internal/Magento/Framework/Exception/LocalizedException.php index 0b1d5f1998bfb..1fe85bc7b3018 100644 --- a/lib/internal/Magento/Framework/Exception/LocalizedException.php +++ b/lib/internal/Magento/Framework/Exception/LocalizedException.php @@ -33,7 +33,7 @@ class LocalizedException extends \Exception public function __construct(Phrase $phrase, \Exception $cause = null, $code = 0) { $this->phrase = $phrase; - parent::__construct($phrase->render(), intval($code), $cause); + parent::__construct($phrase->render(), (int)$code, $cause); } /** diff --git a/lib/internal/Magento/Framework/Filesystem/Driver/File.php b/lib/internal/Magento/Framework/Filesystem/Driver/File.php index 6f9c24344f677..c236228a00b75 100644 --- a/lib/internal/Magento/Framework/Filesystem/Driver/File.php +++ b/lib/internal/Magento/Framework/Filesystem/Driver/File.php @@ -13,6 +13,7 @@ /** * Class File + * * @package Magento\Framework\Filesystem\Driver * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ @@ -405,7 +406,14 @@ public function deleteDirectory($path) $this->deleteFile($entity->getPathname()); } } - $result = @rmdir($this->getScheme() . $path); + + $fullPath = $this->getScheme() . $path; + if (is_link($fullPath)) { + $result = @unlink($fullPath); + } else { + $result = @rmdir($fullPath); + } + if (!$result) { throw new FileSystemException( new \Magento\Framework\Phrase( @@ -836,6 +844,8 @@ public function fileUnlock($resource) } /** + * Returns an absolute path for the given one. + * * @param string $basePath * @param string $path * @param string|null $scheme @@ -872,7 +882,8 @@ public function getRelativePath($basePath, $path = null) } /** - * Fixes path separator + * Fixes path separator. + * * Utility method. * * @param string $path diff --git a/lib/internal/Magento/Framework/Filter/Template/Tokenizer/Variable.php b/lib/internal/Magento/Framework/Filter/Template/Tokenizer/Variable.php index 67c2d17abe208..574ef9faf74a5 100644 --- a/lib/internal/Magento/Framework/Filter/Template/Tokenizer/Variable.php +++ b/lib/internal/Magento/Framework/Filter/Template/Tokenizer/Variable.php @@ -313,6 +313,6 @@ public function getNumber() if (!$this->isNumeric()) { $this->prev(); } - return floatval($value); + return (float)$value; } } diff --git a/lib/internal/Magento/Framework/HTTP/Client/Curl.php b/lib/internal/Magento/Framework/HTTP/Client/Curl.php index 788439aa4ff0d..57a5535751f09 100644 --- a/lib/internal/Magento/Framework/HTTP/Client/Curl.php +++ b/lib/internal/Magento/Framework/HTTP/Client/Curl.php @@ -435,7 +435,7 @@ protected function parseHeaders($ch, $data) if (count($line) != 3) { $this->doError("Invalid response line returned from server: " . $data); } - $this->_responseStatus = intval($line[1]); + $this->_responseStatus = (int)$line[1]; } else { $name = $value = ''; $out = explode(": ", trim($data), 2); diff --git a/lib/internal/Magento/Framework/HTTP/Client/Socket.php b/lib/internal/Magento/Framework/HTTP/Client/Socket.php index d229baa5dd476..883a4e0b75964 100644 --- a/lib/internal/Magento/Framework/HTTP/Client/Socket.php +++ b/lib/internal/Magento/Framework/HTTP/Client/Socket.php @@ -424,7 +424,7 @@ protected function processResponse() if (count($line) != 3) { return $this->doError("Invalid response line returned from server: " . $responseLine); } - $this->_responseStatus = intval($line[1]); + $this->_responseStatus = (int)$line[1]; $this->processResponseHeaders(); $this->processRedirect(); diff --git a/lib/internal/Magento/Framework/Image.php b/lib/internal/Magento/Framework/Image.php index 40c0767293ace..190358d58edd3 100644 --- a/lib/internal/Magento/Framework/Image.php +++ b/lib/internal/Magento/Framework/Image.php @@ -261,7 +261,7 @@ public function instruction() */ public function setImageBackgroundColor($color) { - $this->_adapter->imageBackgroundColor = intval($color); + $this->_adapter->imageBackgroundColor = (int)$color; } /** diff --git a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php index 608907abcc0e1..05f5f01efb29e 100644 --- a/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php +++ b/lib/internal/Magento/Framework/Interception/Code/Generator/Interceptor.php @@ -113,7 +113,7 @@ protected function _getMethodInfo(\ReflectionMethod $method) "} else {\n" . " return \$this->___callPlugins('{$method->getName()}', func_get_args(), \$pluginInfo);\n" . "}", - 'returnType' => $method->getReturnType(), + 'returnType' => $this->getReturnTypeValue($method->getReturnType()), 'docblock' => ['shortDescription' => '{@inheritdoc}'], ]; @@ -183,4 +183,23 @@ protected function _validateData() } return $result; } + + /** + * Returns return type + * + * @param mixed $returnType + * @return null|string + */ + private function getReturnTypeValue($returnType) + { + $returnTypeValue = null; + + if ($returnType) { + $returnTypeValue = ((string)$returnType === 'self') + ? $this->getSourceClassName() + : (string)$returnType; + } + + return $returnTypeValue; + } } diff --git a/lib/internal/Magento/Framework/Mail/MailMessageInterface.php b/lib/internal/Magento/Framework/Mail/MailMessageInterface.php new file mode 100644 index 0000000000000..4f16ad4392c0c --- /dev/null +++ b/lib/internal/Magento/Framework/Mail/MailMessageInterface.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Mail; + +/** + * Mail Message interface + */ +interface MailMessageInterface extends MessageInterface +{ + /** + * Set mail message body in HTML format. + * + * @param string $html + * @return $this + */ + public function setBodyHtml($html); + + /** + * Set mail message body in text format. + * + * @param string $text + * @return $this + */ + public function setBodyText($text); + + /** + * Get message source code. + * + * @return string + */ + public function getRawMessage(); +} diff --git a/lib/internal/Magento/Framework/Mail/Message.php b/lib/internal/Magento/Framework/Mail/Message.php index f9511a260da1a..c8f0e75c52800 100644 --- a/lib/internal/Magento/Framework/Mail/Message.php +++ b/lib/internal/Magento/Framework/Mail/Message.php @@ -1,60 +1,176 @@ <?php /** - * Mail Message - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Mail; -class Message extends \Zend_Mail implements MessageInterface +use Zend\Mime\Mime; +use Zend\Mime\Part; + +class Message implements MailMessageInterface { /** + * @var \Zend\Mail\Message + */ + private $zendMessage; + + /** + * Message type + * + * @var string + */ + private $messageType = self::TYPE_TEXT; + + /** + * Initialize dependencies. + * * @param string $charset */ public function __construct($charset = 'utf-8') { - parent::__construct($charset); - $this->setHeaderEncoding(\Zend_Mime::ENCODING_BASE64); + $this->zendMessage = new \Zend\Mail\Message(); + $this->zendMessage->setEncoding($charset); } /** - * Message type + * {@inheritdoc} * - * @var string + * @deprecated + * @see \Magento\Framework\Mail\Message::setBodyText + * @see \Magento\Framework\Mail\Message::setBodyHtml */ - protected $messageType = self::TYPE_TEXT; + public function setMessageType($type) + { + $this->messageType = $type; + return $this; + } /** - * Set message body + * {@inheritdoc} * - * @param string $body - * @return $this + * @deprecated + * @see \Magento\Framework\Mail\Message::setBodyText + * @see \Magento\Framework\Mail\Message::setBodyHtml */ public function setBody($body) { - return $this->messageType == self::TYPE_TEXT ? $this->setBodyText($body) : $this->setBodyHtml($body); + if (is_string($body) && $this->messageType === MailMessageInterface::TYPE_HTML) { + $body = $this->createHtmlMimeFromString($body); + } + $this->zendMessage->setBody($body); + return $this; } /** - * Set message body - * - * @return string + * @inheritdoc + */ + public function setSubject($subject) + { + $this->zendMessage->setSubject($subject); + return $this; + } + + /** + * @inheritdoc + */ + public function getSubject() + { + return $this->zendMessage->getSubject(); + } + + /** + * @inheritdoc */ public function getBody() { - return $this->messageType == self::TYPE_TEXT ? $this->getBodyText() : $this->getBodyHtml(); + return $this->zendMessage->getBody(); } /** - * Set message type - * - * @param string $type - * @return $this + * @inheritdoc */ - public function setMessageType($type) + public function setFrom($fromAddress) { - $this->messageType = $type; + $this->zendMessage->setFrom($fromAddress); + return $this; + } + + /** + * @inheritdoc + */ + public function addTo($toAddress) + { + $this->zendMessage->addTo($toAddress); return $this; } + + /** + * @inheritdoc + */ + public function addCc($ccAddress) + { + $this->zendMessage->addCc($ccAddress); + return $this; + } + + /** + * @inheritdoc + */ + public function addBcc($bccAddress) + { + $this->zendMessage->addBcc($bccAddress); + return $this; + } + + /** + * @inheritdoc + */ + public function setReplyTo($replyToAddress) + { + $this->zendMessage->setReplyTo($replyToAddress); + return $this; + } + + /** + * @inheritdoc + */ + public function getRawMessage() + { + return $this->zendMessage->toString(); + } + + /** + * @inheritdoc + */ + public function setBodyHtml($html) + { + $this->setMessageType(self::TYPE_HTML); + return $this->setBody($html); + } + + /** + * @inheritdoc + */ + public function setBodyText($text) + { + $this->setMessageType(self::TYPE_TEXT); + return $this->setBody($text); + } + + /** + * Create HTML mime message from the string. + * + * @param string $htmlBody + * @return \Zend\Mime\Message + */ + private function createHtmlMimeFromString($htmlBody) + { + $htmlPart = new Part($htmlBody); + $htmlPart->setCharset($this->zendMessage->getEncoding()); + $htmlPart->setType(Mime::TYPE_HTML); + $mimeMessage = new \Zend\Mime\Message(); + $mimeMessage->addPart($htmlPart); + return $mimeMessage; + } } diff --git a/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php b/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php index 95f17fed1123c..785c93824a57d 100644 --- a/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php +++ b/lib/internal/Magento/Framework/Mail/Template/TransportBuilderByStore.php @@ -47,7 +47,6 @@ public function __construct( public function setFromByStore($from, $store) { $result = $this->senderResolver->resolve($from, $store); - $this->message->clearFrom(); $this->message->setFrom($result['email'], $result['name']); return $this; diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/MessageTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/MessageTest.php index ec484ede5f29c..0c33e25e0d9d0 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/MessageTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/MessageTest.php @@ -8,7 +8,7 @@ class MessageTest extends \PHPUnit\Framework\TestCase { /** - * @var \PHPUnit\Framework_MockObject + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Mail\Message */ protected $_messageMock; @@ -16,78 +16,33 @@ protected function setUp() { $this->_messageMock = $this->createPartialMock( \Magento\Framework\Mail\Message::class, - ['getBodyText', 'getBodyHtml', 'setBodyText', 'setBodyHtml'] + ['setBody', 'setMessageType'] ); } - /** - * @param string $messageType - * @param string $method - * - * @covers \Magento\Framework\Mail\Message::setBody - * @covers \Magento\Framework\Mail\Message::setMessageType - * @dataProvider setBodyDataProvider - */ - public function testSetBody($messageType, $method) + public function testSetBodyHtml() { - $this->_messageMock->setMessageType($messageType); + $this->_messageMock->expects($this->once()) + ->method('setMessageType') + ->with('text/html'); $this->_messageMock->expects($this->once()) - ->method($method) + ->method('setBody') ->with('body'); - $this->_messageMock->setBody('body'); + $this->_messageMock->setBodyHtml('body'); } - /** - * @return array - */ - public function setBodyDataProvider() + public function testSetBodyText() { - return [ - [ - 'messageType' => 'text/plain', - 'method' => 'setBodyText', - ], - [ - 'messageType' => 'text/html', - 'method' => 'setBodyHtml' - ] - ]; - } - - /** - * @param string $messageType - * @param string $method - * - * @covers \Magento\Framework\Mail\Message::getBody - * @covers \Magento\Framework\Mail\Message::setMessageType - * @dataProvider getBodyDataProvider - */ - public function testGetBody($messageType, $method) - { - $this->_messageMock->setMessageType($messageType); - $this->_messageMock->expects($this->once()) - ->method($method); + ->method('setMessageType') + ->with('text/plain'); - $this->_messageMock->getBody('body'); - } + $this->_messageMock->expects($this->once()) + ->method('setBody') + ->with('body'); - /** - * @return array - */ - public function getBodyDataProvider() - { - return [ - [ - 'messageType' => 'text/plain', - 'method' => 'getBodyText', - ], - [ - 'messageType' => 'text/html', - 'method' => 'getBodyHtml' - ] - ]; + $this->_messageMock->setBodyText('body'); } } diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php index 58c9b045eed8c..80df2887a3a93 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/Template/TransportBuilderByStoreTest.php @@ -58,9 +58,6 @@ public function testSetFromByStore() ->method('setFrom') ->with('from@example.com', 'name') ->willReturnSelf(); - $this->messageMock->expects($this->once()) - ->method('clearFrom') - ->willReturnSelf(); $this->model->setFromByStore($sender, $store); } diff --git a/lib/internal/Magento/Framework/Mail/Test/Unit/TransportTest.php b/lib/internal/Magento/Framework/Mail/Test/Unit/TransportTest.php index be735e3a6fe2e..3cbe6fc923cdd 100644 --- a/lib/internal/Magento/Framework/Mail/Test/Unit/TransportTest.php +++ b/lib/internal/Magento/Framework/Mail/Test/Unit/TransportTest.php @@ -7,48 +7,17 @@ class TransportTest extends \PHPUnit\Framework\TestCase { - /** - * @var \PHPUnit\Framework_MockObject - */ - protected $messageMock; - - /** - * @var \Magento\Framework\Mail\Transport - */ - protected $transport; - - protected function setUp() - { - $this->messageMock = $this->createMock(\Magento\Framework\Mail\Message::class); - $this->transport = new \Magento\Framework\Mail\Transport($this->messageMock); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage The message should be an instance of \Zend_Mail - */ - public function testTransportWithIncorrectMessageObject() - { - $this->messageMock = $this->createMock(\Magento\Framework\Mail\MessageInterface::class); - $this->transport = new \Magento\Framework\Mail\Transport($this->messageMock); - } - /** * @covers \Magento\Framework\Mail\Transport::sendMessage * @expectedException \Magento\Framework\Exception\MailException - * @expectedExceptionMessage No body specified + * @expectedExceptionMessage Invalid email; contains no at least one of "To", "Cc", and "Bcc" header */ public function testSendMessageBrokenMessage() { - $this->messageMock->expects($this->any()) - ->method('getParts') - ->will($this->returnValue(['a', 'b'])); - - $this->transport->sendMessage(); - } + $transport = new \Magento\Framework\Mail\Transport( + new \Magento\Framework\Mail\Message() + ); - public function testGetMessage() - { - $this->assertSame($this->messageMock, $this->transport->getMessage()); + $transport->sendMessage(); } } diff --git a/lib/internal/Magento/Framework/Mail/Transport.php b/lib/internal/Magento/Framework/Mail/Transport.php index dc3b48399c865..fd931c5cccecb 100644 --- a/lib/internal/Magento/Framework/Mail/Transport.php +++ b/lib/internal/Magento/Framework/Mail/Transport.php @@ -1,44 +1,48 @@ <?php /** - * Mail Transport * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\Mail; -class Transport extends \Zend_Mail_Transport_Sendmail implements \Magento\Framework\Mail\TransportInterface +use Magento\Framework\Exception\MailException; +use Magento\Framework\Phrase; +use Zend\Mail\Message as ZendMessage; +use Zend\Mail\Transport\Sendmail; + +class Transport implements \Magento\Framework\Mail\TransportInterface { /** - * @var \Magento\Framework\Mail\MessageInterface + * @var Sendmail + */ + private $zendTransport; + + /** + * @var Message */ - protected $_message; + private $message; /** * @param MessageInterface $message - * @param null $parameters - * @throws \InvalidArgumentException + * @param null|string|array|\Traversable $parameters */ - public function __construct(\Magento\Framework\Mail\MessageInterface $message, $parameters = null) + public function __construct(MessageInterface $message, $parameters = null) { - if (!$message instanceof \Zend_Mail) { - throw new \InvalidArgumentException('The message should be an instance of \Zend_Mail'); - } - parent::__construct($parameters); - $this->_message = $message; + $this->zendTransport = new Sendmail($parameters); + $this->message = $message; } /** - * Send a mail using this transport - * - * @return void - * @throws \Magento\Framework\Exception\MailException + * @inheritdoc */ public function sendMessage() { try { - parent::send($this->_message); + $this->zendTransport->send( + ZendMessage::fromString($this->message->getRawMessage()) + ); } catch (\Exception $e) { - throw new \Magento\Framework\Exception\MailException(new \Magento\Framework\Phrase($e->getMessage()), $e); + throw new MailException(new Phrase($e->getMessage()), $e); } } @@ -47,6 +51,6 @@ public function sendMessage() */ public function getMessage() { - return $this->_message; + return $this->message; } } diff --git a/lib/internal/Magento/Framework/Math/Random.php b/lib/internal/Magento/Framework/Math/Random.php index 7cb70c9a822cc..d2f9b30344225 100644 --- a/lib/internal/Magento/Framework/Math/Random.php +++ b/lib/internal/Magento/Framework/Math/Random.php @@ -5,6 +5,9 @@ */ namespace Magento\Framework\Math; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Phrase; + /** * Random data generator * @@ -24,41 +27,24 @@ class Random /**#@-*/ /** - * Get random string + * Get random string. * * @param int $length * @param null|string $chars + * * @return string - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function getRandomString($length, $chars = null) { $str = ''; if (null === $chars) { - $chars = self::CHARS_LOWERS . self::CHARS_UPPERS . self::CHARS_DIGITS; + $chars = self::CHARS_LOWERS.self::CHARS_UPPERS.self::CHARS_DIGITS; } - if (function_exists('openssl_random_pseudo_bytes')) { - // use openssl lib if it is installed - for ($i = 0, $lc = strlen($chars) - 1; $i < $length; $i++) { - $bytes = openssl_random_pseudo_bytes(PHP_INT_SIZE); - $hex = bin2hex($bytes); // hex() doubles the length of the string - $rand = abs(hexdec($hex) % $lc); // random integer from 0 to $lc - $str .= $chars[$rand]; // random character in $chars - } - } elseif ($fp = @fopen('/dev/urandom', 'rb')) { - // attempt to use /dev/urandom if it exists but openssl isn't available - for ($i = 0, $lc = strlen($chars) - 1; $i < $length; $i++) { - $bytes = @fread($fp, PHP_INT_SIZE); - $hex = bin2hex($bytes); // hex() doubles the length of the string - $rand = abs(hexdec($hex) % $lc); // random integer from 0 to $lc - $str .= $chars[$rand]; // random character in $chars - } - fclose($fp); - } else { - throw new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase("Please make sure you have 'openssl' extension installed") - ); + $charsMaxKey = mb_strlen($chars) - 1; + for ($i = 0; $i < $length; $i++) { + $str .= $chars[self::getRandomNumber(0, $charsMaxKey)]; } return $str; @@ -70,44 +56,28 @@ public function getRandomString($length, $chars = null) * @param $min [optional] * @param $max [optional] * @return int A random integer value between min (or 0) and max - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public static function getRandomNumber($min = 0, $max = null) { if (null === $max) { $max = mt_getrandmax(); } - $range = $max - $min + 1; - $offset = 0; - - if (function_exists('openssl_random_pseudo_bytes')) { - // use openssl lib if it is installed - $bytes = openssl_random_pseudo_bytes(PHP_INT_SIZE); - $hex = bin2hex($bytes); // hex() doubles the length of the string - $offset = abs(hexdec($hex) % $range); // random integer from 0 to $range - } elseif ($fp = @fopen('/dev/urandom', 'rb')) { - // attempt to use /dev/urandom if it exists but openssl isn't available - $bytes = @fread($fp, PHP_INT_SIZE); - $hex = bin2hex($bytes); // hex() doubles the length of the string - $offset = abs(hexdec($hex) % $range); // random integer from 0 to $range - fclose($fp); - } else { - throw new \Magento\Framework\Exception\LocalizedException( - new \Magento\Framework\Phrase("Please make sure you have 'openssl' extension installed") - ); + if ($max < $min) { + throw new LocalizedException(new Phrase('Invalid range given.')); } - return $min + $offset; // random integer from $min to $max + return random_int($min, $max); } /** - * Generate a hash from unique ID + * Generate a hash from unique ID. * * @param string $prefix * @return string */ public function getUniqueHash($prefix = '') { - return $prefix . md5(uniqid(microtime() . self::getRandomNumber(), true)); + return $prefix . $this->getRandomString(32); } } diff --git a/lib/internal/Magento/Framework/Mview/View.php b/lib/internal/Magento/Framework/Mview/View.php index 1f1bde6bb021c..fca827ca2505f 100644 --- a/lib/internal/Magento/Framework/Mview/View.php +++ b/lib/internal/Magento/Framework/Mview/View.php @@ -285,7 +285,7 @@ public function update() for ($versionFrom = $lastVersionId; $versionFrom < $currentVersionId; $versionFrom += $versionBatchSize) { // Don't go past the current version for atomicy. $versionTo = min($currentVersionId, $versionFrom + $versionBatchSize); - $ids = $this->getChangelog()->getList($versionFrom, $versionTo); + $ids = array_map('intval', $this->getChangelog()->getList($versionFrom, $versionTo)); // We run the actual indexer in batches. Chunked AFTER loading to avoid duplicates in separate chunks. $chunks = array_chunk($ids, $batchSize); diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php index 0aaf67b5c7cfc..39cbea74847f9 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Proxy.php @@ -165,7 +165,7 @@ protected function _getMethodInfo(\ReflectionMethod $method) 'parameters' => $parameters, 'body' => $this->_getMethodBody($method->getName(), $parameterNames), 'docblock' => ['shortDescription' => '{@inheritdoc}'], - 'returnType' => $method->getReturnType(), + 'returnType' => $this->getReturnTypeValue($method->getReturnType()), ]; return $methodInfo; @@ -245,4 +245,23 @@ protected function _validateData() } return $result; } + + /** + * Returns return type + * + * @param mixed $returnType + * @return null|string + */ + private function getReturnTypeValue($returnType) + { + $returnTypeValue = null; + + if ($returnType) { + $returnTypeValue = ((string)$returnType === 'self') + ? $this->getSourceClassName() + : (string)$returnType; + } + + return $returnTypeValue; + } } diff --git a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php index b1cdb53288216..e0e5aa05a0bd6 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php +++ b/lib/internal/Magento/Framework/ObjectManager/Code/Generator/Repository.php @@ -156,9 +156,6 @@ protected function _getCollectionFactoryClassName() protected function _getPersistorClassName() { $target = $this->getSourceClassName(); -// if (substr($target, -9) == 'Interface') { -// $target = substr($target, 1, strlen($target) -9); -// } return $target . 'Persistor'; } diff --git a/lib/internal/Magento/Framework/Reflection/MethodsMap.php b/lib/internal/Magento/Framework/Reflection/MethodsMap.php index 944cd8771ee33..6b0ddfbfc2127 100644 --- a/lib/internal/Magento/Framework/Reflection/MethodsMap.php +++ b/lib/internal/Magento/Framework/Reflection/MethodsMap.php @@ -94,6 +94,8 @@ public function getMethodReturnType($typeName, $methodName) * 'validatePassword' => 'boolean' * ] * </pre> + * @throws \InvalidArgumentException if methods don't have annotation + * @throws \ReflectionException for missing DocBock or invalid reflection class */ public function getMethodsMap($interfaceName) { @@ -148,6 +150,8 @@ public function getMethodParams($serviceClassName, $serviceMethodName) * * @param string $interfaceName * @return array + * @throws \ReflectionException for missing DocBock or invalid reflection class + * @throws \InvalidArgumentException if methods don't have annotation */ private function getMethodMapViaReflection($interfaceName) { diff --git a/lib/internal/Magento/Framework/Serialize/Serializer/Sensitive.php b/lib/internal/Magento/Framework/Serialize/Serializer/Sensitive.php new file mode 100644 index 0000000000000..248800ccf2baf --- /dev/null +++ b/lib/internal/Magento/Framework/Serialize/Serializer/Sensitive.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Framework\Serialize\Serializer; + +use Magento\Framework\Encryption\EncryptorInterface; +use Magento\Framework\Serialize\SerializerInterface; + +/** + * Used to serialize sensitive data. + */ +class Sensitive implements SerializerInterface +{ + /** + * @var EncryptorInterface + */ + private $encryptor; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @param EncryptorInterface $encryptor + * @param SerializerInterface $serializer + */ + public function __construct( + EncryptorInterface $encryptor, + SerializerInterface $serializer + ) { + $this->encryptor = $encryptor; + $this->serializer = $serializer; + } + + /** + * @inheritDoc + */ + public function serialize($data) + { + $serialized = $this->serializer->serialize($data); + if (is_string($serialized)) { + $serialized = $this->encryptor->encrypt($serialized); + } + + return $serialized; + } + + /** + * @inheritDoc + */ + public function unserialize($string) + { + $string = $this->encryptor->decrypt($string); + + return $this->serializer->unserialize($string); + } +} diff --git a/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php b/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php index 53af1847d1012..447523214cb78 100644 --- a/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php +++ b/lib/internal/Magento/Framework/Session/SaveHandler/Redis.php @@ -14,13 +14,28 @@ use Magento\Framework\Filesystem; use Magento\Framework\App\Filesystem\DirectoryList; -class Redis extends \Cm\RedisSession\Handler +class Redis implements \SessionHandlerInterface { + /** + * @var ConfigInterface + */ + private $config; + + /** + * @var LoggerInterface + */ + private $logger; + /** * @var Filesystem */ private $filesystem; + /** + * @var \Cm\RedisSession\Handler[] + */ + private $connection; + /** * @param ConfigInterface $config * @param LoggerInterface $logger @@ -29,23 +44,116 @@ class Redis extends \Cm\RedisSession\Handler */ public function __construct(ConfigInterface $config, LoggerInterface $logger, Filesystem $filesystem) { + $this->config = $config; + $this->logger = $logger; $this->filesystem = $filesystem; - try { - parent::__construct($config, $logger); - } catch (ConnectionFailedException $e) { - throw new SessionException(new Phrase($e->getMessage())); + } + + /** + * Get connection + * + * @return \Cm\RedisSession\Handler + * @throws SessionException + */ + private function getConnection() + { + $pid = getmypid(); + if (!isset($this->connection[$pid])) { + try { + $this->connection[$pid] = new \Cm\RedisSession\Handler($this->config, $this->logger); + } catch (ConnectionFailedException $e) { + throw new SessionException(new Phrase($e->getMessage())); + } } + return $this->connection[$pid]; } /** - * {@inheritdoc} + * Open session + * + * @param string $savePath ignored + * @param string $sessionName ignored + * @return bool + * @throws SessionException + */ + public function open($savePath, $sessionName) + { + return $this->getConnection()->open($savePath, $sessionName); + } + + /** + * Fetch session data + * + * @param string $sessionId + * @return string + * @throws ConcurrentConnectionsExceededException + * @throws SessionException */ public function read($sessionId) { try { - return parent::read($sessionId); + return $this->getConnection()->read($sessionId); } catch (ConcurrentConnectionsExceededException $e) { require $this->filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/503.php'); } } + + /** + * Update session + * + * @param string $sessionId + * @param string $sessionData + * @return boolean + * @throws SessionException + */ + public function write($sessionId, $sessionData) + { + return $this->getConnection()->write($sessionId, $sessionData); + } + + /** + * Destroy session + * + * @param string $sessionId + * @return boolean + * @throws SessionException + */ + public function destroy($sessionId) + { + return $this->getConnection()->destroy($sessionId); + } + + /** + * Overridden to prevent calling getLifeTime at shutdown + * + * @return bool + * @throws SessionException + */ + public function close() + { + return $this->getConnection()->close(); + } + + /** + * Garbage collection + * + * @param int $maxLifeTime ignored + * @return boolean + * @throws SessionException + */ + public function gc($maxLifeTime) + { + return $this->getConnection()->gc($maxLifeTime); + } + + /** + * Get the number of failed lock attempts + * + * @return int + * @throws SessionException + */ + public function getFailedLockAttempts() + { + return $this->getConnection()->getFailedLockAttempts(); + } } diff --git a/lib/internal/Magento/Framework/System/Ftp.php b/lib/internal/Magento/Framework/System/Ftp.php index 9b25d0631feb3..15bcd583bd789 100644 --- a/lib/internal/Magento/Framework/System/Ftp.php +++ b/lib/internal/Magento/Framework/System/Ftp.php @@ -128,7 +128,7 @@ public function validateConnectionString($string) public function connect($string, $timeout = 900) { $params = $this->validateConnectionString($string); - $port = isset($params['port']) ? intval($params['port']) : 21; + $port = isset($params['port']) ? (int)$params['port'] : 21; $this->_conn = ftp_connect($params['host'], $port, $timeout); @@ -189,7 +189,7 @@ public function getcwd() if (empty($data[1])) { return false; } - if (intval($data[0]) != 257) { + if ((int)$data[0] != 257) { return false; } $out = trim($data[1], '"'); diff --git a/lib/internal/Magento/Framework/Test/Unit/Communication/Config/ValidatorTest.php b/lib/internal/Magento/Framework/Test/Unit/Communication/Config/ValidatorTest.php new file mode 100644 index 0000000000000..55410af176af0 --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/Communication/Config/ValidatorTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\Test\Unit\Communication\Config; + +use Magento\Framework\Communication\Config\Validator; +use Magento\Framework\Reflection\MethodsMap; +use Magento\Framework\Reflection\TypeProcessor; + +/** + * Unit test for \Magento\Framework\Communication\Config\Validator class + */ +class ValidatorTest extends \PHPUnit\Framework\TestCase +{ + /** + * @var TypeProcessor|\PHPUnit_Framework_MockObject_MockObject + */ + protected $typeProcessor; + + /** + * @var MethodsMap|\PHPUnit_Framework_MockObject_MockObject + */ + protected $methodsMap; + + public function setUp() + { + $this->methodsMap = $this->createMock(MethodsMap::class); + + $this->methodsMap->expects(static::any()) + ->method('getMethodsMap') + ->will($this->throwException(new \InvalidArgumentException('message', 333))); + + $this->typeProcessor = $this->createMock(TypeProcessor::class); + $this->typeProcessor->expects(static::any()) + ->method('isTypeSimple') + ->willReturn(false); + + $this->typeProcessor->expects(static::any()) + ->method('isTypeSimple') + ->willReturn(false); + } + + /** + * @expectedException \LogicException + * @expectedExceptionCode 333 + * @expectedExceptionMessage Response schema definition has service class with wrong annotated methods + */ + public function testValidateResponseSchemaType() + { + /** @var Validator $validator */ + $validator = new Validator($this->typeProcessor, $this->methodsMap); + $validator->validateResponseSchemaType('123', '123'); + } + + /** + * @expectedException \LogicException + * @expectedExceptionCode 333 + * @expectedExceptionMessage Request schema definition has service class with wrong annotated methods + */ + public function testValidateRequestSchemaType() + { + /** @var Validator $validator */ + $validator = new Validator($this->typeProcessor, $this->methodsMap); + $validator->validateRequestSchemaType('123', '123'); + } +} diff --git a/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php b/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php index ab1bca9f4548c..43bfd46c1193a 100644 --- a/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php +++ b/lib/internal/Magento/Framework/View/Element/Html/Link/Current.php @@ -104,13 +104,13 @@ protected function _toHtml() if ($this->isCurrent()) { $html = '<li class="nav item current">'; $html .= '<strong>' - . $this->escapeHtml((string)new \Magento\Framework\Phrase($this->getLabel())) + . $this->escapeHtml(__($this->getLabel())) . '</strong>'; $html .= '</li>'; } else { $html = '<li class="nav item' . $highlight . '"><a href="' . $this->escapeHtml($this->getHref()) . '"'; $html .= $this->getTitle() - ? ' title="' . $this->escapeHtml((string)new \Magento\Framework\Phrase($this->getTitle())) . '"' + ? ' title="' . $this->escapeHtml(__($this->getTitle())) . '"' : ''; $html .= $this->getAttributesHtml() . '>'; @@ -118,7 +118,7 @@ protected function _toHtml() $html .= '<strong>'; } - $html .= $this->escapeHtml((string)new \Magento\Framework\Phrase($this->getLabel())); + $html .= $this->escapeHtml(__($this->getLabel())); if ($this->getIsHighlighted()) { $html .= '</strong>'; diff --git a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php index 7312f7546df9b..5ec7fcbce187d 100644 --- a/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php +++ b/lib/internal/Magento/Framework/View/Element/UiComponent/Context.php @@ -292,8 +292,8 @@ public function addButtons(array $buttons, UiComponentInterface $component) */ public function sortButtons(array $itemA, array $itemB) { - $sortOrderA = isset($itemA['sort_order']) ? intval($itemA['sort_order']) : 0; - $sortOrderB = isset($itemB['sort_order']) ? intval($itemB['sort_order']) : 0; + $sortOrderA = isset($itemA['sort_order']) ? (int)$itemA['sort_order'] : 0; + $sortOrderB = isset($itemB['sort_order']) ? (int)$itemB['sort_order'] : 0; return $sortOrderA - $sortOrderB; } diff --git a/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php b/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php index 20bffff6495de..c5a5cc71a45ff 100644 --- a/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php +++ b/lib/internal/Magento/Framework/Webapi/ErrorProcessor.php @@ -317,7 +317,7 @@ public function apiShutdownFunction() protected function _saveFatalErrorReport($reportData) { $this->directoryWrite->create('report/api'); - $reportId = abs(intval(microtime(true) * random_int(100, 1000))); + $reportId = abs((int)microtime(true) * random_int(100, 1000)); $this->directoryWrite->writeFile('report/api/' . $reportId, $this->serializer->serialize($reportData)); return $reportId; } diff --git a/lib/internal/Magento/Framework/Webapi/Rest/Response/FieldsFilter.php b/lib/internal/Magento/Framework/Webapi/Rest/Response/FieldsFilter.php index 87be46c111545..eb8403501279f 100644 --- a/lib/internal/Magento/Framework/Webapi/Rest/Response/FieldsFilter.php +++ b/lib/internal/Magento/Framework/Webapi/Rest/Response/FieldsFilter.php @@ -6,6 +6,8 @@ namespace Magento\Framework\Webapi\Rest\Response; +use Magento\Framework\Api\AbstractExtensibleObject; +use Magento\Framework\Api\AttributeInterface; use Magento\Framework\Webapi\Rest\Request as RestRequest; /** @@ -178,10 +180,47 @@ protected function recursiveArrayIntersectKey(array $array1, array $array2) //If the field in array2 (filter) is not present in array1 (response) it will be removed after intersect $arrayIntersect = array_intersect_key($array1, $array2); foreach ($arrayIntersect as $key => &$value) { + if ($key == AbstractExtensibleObject::CUSTOM_ATTRIBUTES_KEY + && is_array($array2[AbstractExtensibleObject::CUSTOM_ATTRIBUTES_KEY]) + ) { + $value = $this->filterCustomAttributes( + $value, + $array2[AbstractExtensibleObject::CUSTOM_ATTRIBUTES_KEY] + ); + continue; + } if (is_array($value) && is_array($array2[$key])) { $value = $this->applyFilter($value, $array2[$key]); } } return $arrayIntersect; } + + /** + * Filter for custom attributes. + * + * @param array $item + * @param array $filter + * @return array + */ + private function filterCustomAttributes(array $item, array $filter) : array + { + $fieldResult = []; + foreach ($item as $key => $field) { + $filterKeys = array_keys($filter); + if (in_array($field[AttributeInterface::ATTRIBUTE_CODE], $filterKeys)) { + $fieldResult[$key][AttributeInterface::ATTRIBUTE_CODE] = $field[AttributeInterface::ATTRIBUTE_CODE]; + $fieldResult[$key][AttributeInterface::VALUE] = $field[AttributeInterface::VALUE]; + } else { + if (isset($filter[AttributeInterface::ATTRIBUTE_CODE])) { + $fieldResult[$key][AttributeInterface::ATTRIBUTE_CODE] = $field[AttributeInterface::ATTRIBUTE_CODE]; + } + if (isset($filter[AttributeInterface::VALUE])) { + $fieldResult[$key][AttributeInterface::VALUE] = $field[AttributeInterface::VALUE]; + } + } + } + + return $fieldResult; + } } diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index a4b14fddf29b8..ededfebaf94ab 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -2,7 +2,7 @@ "name": "magento/framework", "description": "N/A", "type": "magento2-library", - "version": "101.0.5", + "version": "101.0.6", "license": [ "OSL-3.0", "AFL-3.0" diff --git a/lib/web/css/source/lib/_typography.less b/lib/web/css/source/lib/_typography.less index 07128abbf7fcf..62529fe08d1c8 100644 --- a/lib/web/css/source/lib/_typography.less +++ b/lib/web/css/source/lib/_typography.less @@ -37,7 +37,7 @@ } // Rem line height -.lib-line-height(@heightValue) when not (@heightValue = false) and not (ispercentage(@heightValue)) { +.lib-line-height(@heightValue) when not (@heightValue = false) and not (@heightValue = normal) and not (ispercentage(@heightValue)) { .lib-font-size-value(@heightValue); .lib-css(line-height, @fontValue); } @@ -46,6 +46,10 @@ .lib-css(line-height, @heightValue); } +.lib-line-height(@heightValue) when (@heightValue = normal) { + .lib-css(line-height, @heightValue); +} + .lib-wrap-words() { overflow-wrap: break-word; word-wrap: break-word; diff --git a/lib/web/fotorama/fotorama.js b/lib/web/fotorama/fotorama.js index e388ec0a23038..4062c501cb798 100644 --- a/lib/web/fotorama/fotorama.js +++ b/lib/web/fotorama/fotorama.js @@ -831,7 +831,7 @@ fotoramaVersion = '4.6.4'; } type = 'youtube'; } - } else if (href.host.match(/youtube\.com|youtu\.be/)) { + } else if (href.host.match(/youtube\.com|youtu\.be|youtube-nocookie.com/)) { id = href.pathname.replace(/^\/(embed\/|v\/)?/, '').replace(/\/.*/, ''); type = 'youtube'; } else if (href.host.match(/vimeo\.com/)) { @@ -1945,10 +1945,10 @@ fotoramaVersion = '4.6.4'; if (e.keyCode === 27) { catched = true; that.cancelFullScreen(); - } else if ((e.shiftKey && e.keyCode === 32 && allowKey('space')) || (e.keyCode === 37 && allowKey('left')) || (e.keyCode === 38 && allowKey('up') && $(':focus').attr('data-gallery-role'))) { + } else if ((e.shiftKey && e.keyCode === 32 && allowKey('space')) || (!e.altKey && !e.metaKey && e.keyCode === 37 && allowKey('left')) || (e.keyCode === 38 && allowKey('up') && $(':focus').attr('data-gallery-role'))) { that.longPress.progress(); index = '<'; - } else if ((e.keyCode === 32 && allowKey('space')) || (e.keyCode === 39 && allowKey('right')) || (e.keyCode === 40 && allowKey('down') && $(':focus').attr('data-gallery-role'))) { + } else if ((e.keyCode === 32 && allowKey('space')) || (!e.altKey && !e.metaKey && e.keyCode === 39 && allowKey('right')) || (e.keyCode === 40 && allowKey('down') && $(':focus').attr('data-gallery-role'))) { that.longPress.progress(); index = '>'; } else if (e.keyCode === 36 && allowKey('home')) { @@ -2092,7 +2092,7 @@ fotoramaVersion = '4.6.4'; o_navTop = opts.navposition === 'top'; classes.remove.push(selectClass); - $arrs.toggle(opts.arrows); + $arrs.toggle(!!opts.arrows); } else { o_nav = false; $arrs.hide(); @@ -2562,7 +2562,7 @@ fotoramaVersion = '4.6.4'; thisData.t > rightLimit : thisData.l > rightLimit; specialMeasures.w = thisData.w; - if (thisData.l + thisData.w < leftLimit + if ((opts.navdir !== 'vertical' && thisData.l + thisData.w < leftLimit) || exceedLimit || callFit(thisData.$img, specialMeasures)) return; @@ -3547,7 +3547,7 @@ fotoramaVersion = '4.6.4'; if ((result.moved || (toggleControlsFLAG && result.pos !== result.newPos && !result.control)) && result.$target[0] !== $fullscreenIcon[0]) { var index = getIndexByPos(result.newPos, measures.w, opts.margin, repositionIndex); - + that.show({ index: index, time: o_fade ? o_transitionDuration : result.time, diff --git a/lib/web/fotorama/fotorama.min.js b/lib/web/fotorama/fotorama.min.js index 808ae6a60db9b..e8eb9fbda63ef 100644 --- a/lib/web/fotorama/fotorama.min.js +++ b/lib/web/fotorama/fotorama.min.js @@ -1,4 +1,4 @@ /*! * Fotorama 4.6.4 | http://fotorama.io/license/ */ -fotoramaVersion="4.6.4";(function(bo,k,a3,bV,aP){var ag="fotorama",bH="fotorama__fullscreen",ae=ag+"__wrap",ah=ae+"--css2",aX=ae+"--css3",bt=ae+"--video",ar=ae+"--fade",aw=ae+"--slide",P=ae+"--no-controls",aM=ae+"--no-shadows",U=ae+"--pan-y",a0=ae+"--rtl",az=ae+"--only-active",bN=ae+"--no-captions",f=ae+"--toggle-arrows",a7=ag+"__stage",x=a7+"__frame",l=x+"--video",B=a7+"__shaft",aB=ag+"__grab",bC=ag+"__pointer",aK=ag+"__arr",F=aK+"--disabled",bc=aK+"--prev",r=aK+"--next",bO=ag+"__nav",bq=bO+"-wrap",aH=bO+"__shaft",b=bq+"--vertical",ax=bq+"--list",bZ=bq+"--horizontal",bW=bO+"--dots",ai=bO+"--thumbs",aG=bO+"__frame",br=ag+"__fade",al=br+"-front",n=br+"-rear",aW=ag+"__shadow",bz=aW+"s",S=bz+"--left",aL=bz+"--right",a2=bz+"--top",aR=bz+"--bottom",a4=ag+"__active",a9=ag+"__select",bs=ag+"--hidden",M=ag+"--fullscreen",aJ=ag+"__fullscreen-icon",bP=ag+"__error",bM=ag+"__loading",c=ag+"__loaded",b3=c+"--full",bg=c+"--img",bR=ag+"__grabbing",J=ag+"__img",Y=J+"--full",bS=ag+"__thumb",b0=bS+"__arr--left",H=bS+"__arr--right",cb=bS+"-border",bd=ag+"__html",af=ag+"-video-container",bJ=ag+"__video",T=bJ+"-play",w=bJ+"-close",au=ag+"_horizontal_ratio",aY=ag+"_vertical_ratio",ca=ag+"__spinner",Z=ca+"--show";var E=bV&&bV.fn.jquery.split(".");if(!E||E[0]<1||(E[0]==1&&E[1]<8)){throw"Fotorama requires jQuery 1.8 or later and will not run without it."}var bx={};var ap=(function(co,ct,cj){var cf="2.8.3",cm={},cD=ct.documentElement,cE="modernizr",cB=ct.createElement(cE),cp=cB.style,cg,cw={}.toString,cy=" -webkit- -moz- -o- -ms- ".split(" "),cd="Webkit Moz O ms",cG=cd.split(" "),cq=cd.toLowerCase().split(" "),ck={},ce={},cu={},cA=[],cv=cA.slice,cc,cz=function(cQ,cS,cK,cR){var cJ,cP,cM,cN,cI=ct.createElement("div"),cO=ct.body,cL=cO||ct.createElement("body");if(parseInt(cK,10)){while(cK--){cM=ct.createElement("div");cM.id=cR?cR[cK]:cE+(cK+1);cI.appendChild(cM)}}cJ=["­",'<style id="s',cE,'">',cQ,"</style>"].join("");cI.id=cE;(cO?cI:cL).innerHTML+=cJ;cL.appendChild(cI);if(!cO){cL.style.background="";cL.style.overflow="hidden";cN=cD.style.overflow;cD.style.overflow="hidden";cD.appendChild(cL)}cP=cS(cI,cQ);if(!cO){cL.parentNode.removeChild(cL);cD.style.overflow=cN}else{cI.parentNode.removeChild(cI)}return !!cP},cs=({}).hasOwnProperty,cC;if(!cl(cs,"undefined")&&!cl(cs.call,"undefined")){cC=function(cI,cJ){return cs.call(cI,cJ)}}else{cC=function(cI,cJ){return((cJ in cI)&&cl(cI.constructor.prototype[cJ],"undefined"))}}if(!Function.prototype.bind){Function.prototype.bind=function cH(cK){var cL=this;if(typeof cL!="function"){throw new TypeError()}var cI=cv.call(arguments,1),cJ=function(){if(this instanceof cJ){var cO=function(){};cO.prototype=cL.prototype;var cN=new cO();var cM=cL.apply(cN,cI.concat(cv.call(arguments)));if(Object(cM)===cM){return cM}return cN}else{return cL.apply(cK,cI.concat(cv.call(arguments)))}};return cJ}}function cr(cI){cp.cssText=cI}function ci(cJ,cI){return cr(cy.join(cJ+";")+(cI||""))}function cl(cJ,cI){return typeof cJ===cI}function cn(cJ,cI){return !!~(""+cJ).indexOf(cI)}function cF(cK,cI){for(var cJ in cK){var cL=cK[cJ];if(!cn(cL,"-")&&cp[cL]!==cj){return cI=="pfx"?cL:true}}return false}function cx(cJ,cM,cL){for(var cI in cJ){var cK=cM[cJ[cI]];if(cK!==cj){if(cL===false){return cJ[cI]}if(cl(cK,"function")){return cK.bind(cL||cM)}return cK}}return false}function i(cM,cI,cL){var cJ=cM.charAt(0).toUpperCase()+cM.slice(1),cK=(cM+" "+cG.join(cJ+" ")+cJ).split(" ");if(cl(cI,"string")||cl(cI,"undefined")){return cF(cK,cI)}else{cK=(cM+" "+(cq).join(cJ+" ")+cJ).split(" ");return cx(cK,cI,cL)}}ck.touch=function(){var cI;if(("ontouchstart" in co)||co.DocumentTouch&&ct instanceof DocumentTouch){cI=true}else{cz(["@media (",cy.join("touch-enabled),("),cE,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(cJ){cI=cJ.offsetTop===9})}return cI};ck.csstransforms3d=function(){var cI=!!i("perspective");if(cI&&"webkitPerspective" in cD.style){cz("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(cJ,cK){cI=cJ.offsetLeft===9&&cJ.offsetHeight===3})}return cI};ck.csstransitions=function(){return i("transition")};for(var ch in ck){if(cC(ck,ch)){cc=ch.toLowerCase();cm[cc]=ck[ch]();cA.push((cm[cc]?"":"no-")+cc)}}cm.addTest=function(cJ,cK){if(typeof cJ=="object"){for(var cI in cJ){if(cC(cJ,cI)){cm.addTest(cI,cJ[cI])}}}else{cJ=cJ.toLowerCase();if(cm[cJ]!==cj){return cm}cK=typeof cK=="function"?cK():cK;if(typeof enableClasses!=="undefined"&&enableClasses){cD.className+=" "+(cK?"":"no-")+cJ}cm[cJ]=cK}return cm};cr("");cB=cg=null;cm._version=cf;cm._prefixes=cy;cm._domPrefixes=cq;cm._cssomPrefixes=cG;cm.testProp=function(cI){return cF([cI])};cm.testAllProps=i;cm.testStyles=cz;cm.prefixed=function(cK,cJ,cI){if(!cJ){return i(cK,"pfx")}else{return i(cK,cJ,cI)}};return cm})(bo,k);var bB={ok:false,is:function(){return false},request:function(){},cancel:function(){},event:"",prefix:""},h="webkit moz o ms khtml".split(" ");if(typeof k.cancelFullScreen!="undefined"){bB.ok=true}else{for(var bv=0,N=h.length;bv<N;bv++){bB.prefix=h[bv];if(typeof k[bB.prefix+"CancelFullScreen"]!="undefined"){bB.ok=true;break}}}if(bB.ok){bB.event=bB.prefix+"fullscreenchange";bB.is=function(){switch(this.prefix){case"":return k.fullScreen;case"webkit":return k.webkitIsFullScreen;default:return k[this.prefix+"FullScreen"]}};bB.request=function(i){return(this.prefix==="")?i.requestFullScreen():i[this.prefix+"RequestFullScreen"]()};bB.cancel=function(i){return(this.prefix==="")?k.cancelFullScreen():k[this.prefix+"CancelFullScreen"]()}}function a6(i){var cc="bez_"+bV.makeArray(arguments).join("_").replace(".","p");if(typeof bV.easing[cc]!=="function"){var cd=function(ck,ci){var cf=[null,null],cl=[null,null],cj=[null,null],ch=function(cm,cn){cj[cn]=3*ck[cn];cl[cn]=3*(ci[cn]-ck[cn])-cj[cn];cf[cn]=1-cj[cn]-cl[cn];return cm*(cj[cn]+cm*(cl[cn]+cm*cf[cn]))},cg=function(cm){return cj[0]+cm*(2*cl[0]+3*cf[0]*cm)},ce=function(co){var cm=co,cn=0,cp;while(++cn<14){cp=ch(cm,0)-co;if(Math.abs(cp)<0.001){break}cm-=cp/cg(cm)}return cm};return function(cm){return ch(ce(cm),1)}};bV.easing[cc]=function(cf,cg,ce,ci,ch){return ci*cd([i[0],i[1]],[i[2],i[3]])(cg/ch)+ce}}return cc}var bf=bV(bo),bw=bV(k),R,I,bT=a3.hash.replace("#","")==="quirks",ac=ap.csstransforms3d,aA=ac&&!bT,aN=ac||k.compatMode==="CSS1Compat",s=bB.ok,am=navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i),bh=!aA||am,aZ=navigator.msPointerEnabled,y="onwheel" in k.createElement("div")?"wheel":k.onmousewheel!==aP?"mousewheel":"DOMMouseScroll",b8=250,ba=300,bY=1400,bQ=5000,b9=2,L=64,bk=500,bE=333,bu="$stageFrame",b7="$navDotFrame",bl="$navThumbFrame",bF="auto",u=a6([0.1,0,0.25,1]),bK=1200,b4=1,Q={width:null,minwidth:null,maxwidth:"100%",height:null,minheight:null,maxheight:null,ratio:null,margin:b9,nav:"dots",navposition:"bottom",navwidth:null,thumbwidth:L,thumbheight:L,thumbmargin:b9,thumbborderwidth:b9,allowfullscreen:false,transition:"slide",clicktransition:null,transitionduration:ba,captions:true,startindex:0,loop:false,autoplay:false,stopautoplayontouch:true,keyboard:false,arrows:true,click:true,swipe:false,trackpad:false,shuffle:false,direction:"ltr",shadows:true,showcaption:true,navdir:"horizontal",navarrows:true,navtype:"thumbs"},p={left:true,right:true,down:true,up:true,space:false,home:false,end:false};function g(){}function bb(cd,cc,i){return Math.max(isNaN(cc)?-Infinity:cc,Math.min(isNaN(i)?Infinity:i,cd))}function bi(cc,i){return cc.match(/ma/)&&cc.match(/-?\d+(?!d)/g)[cc.match(/3d/)?(i==="vertical"?13:12):(i==="vertical"?5:4)]}function aa(cc,i){if(aA){return +bi(cc.css("transform"),i)}else{return +cc.css(i==="vertical"?"top":"left").replace("px","")}}function b2(cd,cc){var i={};if(aA){switch(cc){case"vertical":i.transform="translate3d(0, "+(cd)+"px,0)";break;case"list":break;default:i.transform="translate3d("+(cd)+"px,0,0)";break}}else{cc==="vertical"?i.top=cd:i.left=cd}return i}function b6(i){return{"transition-duration":i+"ms"}}function aV(cc,i){return isNaN(cc)?i:cc}function m(cc,i){return aV(+String(cc).replace(i||"px",""))}function K(i){return/%$/.test(i)?m(i,"%"):aP}function d(cc,i){return aV(K(cc)/100*i,m(cc))}function t(i){return(!isNaN(m(i))||!isNaN(m(i,"%")))&&i}function a8(cc,cd,ce,i){return(cc-(i||0))*(cd+(ce||0))}function by(ce,cc,cd,i){return -Math.round(ce/(cc+(cd||0))-(i||0))}function aO(cd){var cc=cd.data();if(cc.tEnd){return}var ce=cd[0],i={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",msTransition:"MSTransitionEnd",transition:"transitionend"};D(ce,i[ap.prefixed("transition")],function(cf){cc.tProp&&cf.propertyName.match(cc.tProp)&&cc.onEndFn()});cc.tEnd=true}function X(cd,cf,ce,cg){var cc,i=cd.data();if(i){i.onEndFn=function(){if(cc){return}cc=true;clearTimeout(i.tT);ce()};i.tProp=cf;clearTimeout(i.tT);i.tT=setTimeout(function(){i.onEndFn()},cg*1.5);aO(cd)}}function a1(cd,cf){var cc=cd.navdir||"horizontal";if(cd.length){var i=cd.data();if(aA){cd.css(b6(0));i.onEndFn=g;clearTimeout(i.tT)}else{cd.stop()}var ce=bj(cf,function(){return aa(cd,cc)});cd.css(b2(ce,cc));return ce}}function bj(){var cc;for(var cd=0,i=arguments.length;cd<i;cd++){cc=cd?arguments[cd]():arguments[cd];if(typeof cc==="number"){break}}return cc}function aF(cc,i){return Math.round(cc+((i-cc)/1.5))}function aU(){aU.p=aU.p||(a3.protocol==="https:"?"https://":"http://");return aU.p}function ak(cc){var i=k.createElement("a");i.href=cc;return i}function at(i,cd){if(typeof i!=="string"){return i}i=ak(i);var cf,ce;if(i.host.match(/youtube\.com/)&&i.search){cf=i.search.split("v=")[1];if(cf){var cc=cf.indexOf("&");if(cc!==-1){cf=cf.substring(0,cc)}ce="youtube"}}else{if(i.host.match(/youtube\.com|youtu\.be/)){cf=i.pathname.replace(/^\/(embed\/|v\/)?/,"").replace(/\/.*/,"");ce="youtube"}else{if(i.host.match(/vimeo\.com/)){ce="vimeo";cf=i.pathname.replace(/^\/(video\/)?/,"").replace(/\/.*/,"")}}}if((!cf||!ce)&&cd){cf=i.href;ce="custom"}return cf?{id:cf,type:ce,s:i.search.replace(/^\?/,""),p:aU()}:false}function aQ(cg,ce,cf){var cc,i,cd=cg.video;if(cd.type==="youtube"){i=aU()+"img.youtube.com/vi/"+cd.id+"/default.jpg";cc=i.replace(/\/default.jpg$/,"/hqdefault.jpg");cg.thumbsReady=true}else{if(cd.type==="vimeo"){bV.ajax({url:aU()+"vimeo.com/api/v2/video/"+cd.id+".json",dataType:"jsonp",success:function(ch){cg.thumbsReady=true;v(ce,{img:ch[0].thumbnail_large,thumb:ch[0].thumbnail_small},cg.i,cf)}})}else{cg.thumbsReady=true}}return{img:cc,thumb:i}}function v(ch,cd,cf,ci){for(var cg=0,ce=ch.length;cg<ce;cg++){var cj=ch[cg];if(cj.i===cf&&cj.thumbsReady){var cc={videoReady:true};cc[bu]=cc[bl]=cc[b7]=false;ci.splice(cg,1,bV.extend({},cj,cc,cd));break}}}function bI(cc){var ce=[];function cd(cf,ch,cn){var ci=cf.children("img").eq(0),cm=cf.attr("href"),ck=cf.attr("src"),cl=ci.attr("src"),cj=ch.video,cg=cn?at(cm,cj===true):false;if(cg){cm=false}else{cg=cj}i(cf,ci,bV.extend(ch,{video:cg,img:ch.img||cm||ck||cl,thumb:ch.thumb||cl||ck||cm}))}function i(cg,cj,ck){var ci=ck.thumb&&ck.img!==ck.thumb,ch=m(ck.width||cg.attr("width")),cf=m(ck.height||cg.attr("height"));bV.extend(ck,{width:ch,height:cf,thumbratio:bm(ck.thumbratio||(m(ck.thumbwidth||(cj&&cj.attr("width"))||ci||ch)/m(ck.thumbheight||(cj&&cj.attr("height"))||ci||cf)))})}cc.children().each(function(){var cf=bV(this),cg=bA(bV.extend(cf.data(),{id:cf.attr("id")}));if(cf.is("a, img")){cd(cf,cg,true)}else{if(!cf.is(":empty")){i(cf,null,bV.extend(cg,{html:this,_html:cf.html()}))}else{return}}ce.push(cg)});return ce}function W(i){return i.offsetWidth===0&&i.offsetHeight===0}function be(i){return !bV.contains(k.documentElement,i)}function bX(cf,cd,ce,cc){if(!bX.i){bX.i=1;bX.ii=[true]}cc=cc||bX.i;if(typeof bX.ii[cc]==="undefined"){bX.ii[cc]=true}if(cf()){cd()}else{bX.ii[cc]&&setTimeout(function(){bX.ii[cc]&&bX(cf,cd,ce,cc)},ce||100)}return bX.i++}bX.stop=function(cc){bX.ii[cc]=false};function V(ce,cd){var cc=ce.data(),cg=cc.measures;if(cg&&(!cc.l||cc.l.W!==cg.width||cc.l.H!==cg.height||cc.l.r!==cg.ratio||cc.l.w!==cd.w||cc.l.h!==cd.h)){var i=bb(cd.h,0,cg.height),cf=i*cg.ratio;aS.setRatio(ce,cf,i);cc.l={W:cg.width,H:cg.height,r:cg.ratio,w:cd.w,h:cd.h}}return true}function an(i,cd){var cc=i[0];if(cc.styleSheet){cc.styleSheet.cssText=cd}else{i.html(cd)}}function bp(ce,cd,i,cc){return cd===i?false:cc==="vertical"?(ce<=cd?"top":ce>=i?"bottom":"top bottom"):(ce<=cd?"left":ce>=i?"right":"left right")}function z(cc,cd,i){i=i||{};cc.each(function(){var cg=bV(this),cf=cg.data(),ce;if(cf.clickOn){return}cf.clickOn=true;bV.extend(aI(cg,{onStart:function(ch){ce=ch;(i.onStart||g).call(this,ch)},onMove:i.onMove||g,onTouchEnd:i.onTouchEnd||g,onEnd:function(ch){if(ch.moved){return}cd.call(this,ce)}}),{noMove:true})})}function ab(i,cc){return'<div class="'+i+'">'+(cc||"")+"</div>"}function aT(i){return"."+i}function q(i){var cc='<iframe src="'+i.p+i.type+".com/embed/"+i.id+'" frameborder="0" allowfullscreen></iframe>';return cc}function aC(cf){var cc=cf.length;while(cc){var ce=Math.floor(Math.random()*cc--);var cd=cf[cc];cf[cc]=cf[ce];cf[ce]=cd}return cf}function bG(i){return Object.prototype.toString.call(i)=="[object Array]"&&bV.map(i,function(cc){return bV.extend({},cc)})}function bU(i,cd,cc){i.scrollLeft(cd||0).scrollTop(cc||0)}function bA(i){if(i){var cc={};bV.each(i,function(cd,ce){cc[cd.toLowerCase()]=ce});return cc}}function bm(i){if(!i){return}var cc=+i;if(!isNaN(cc)){return cc}else{cc=i.split("/");return +cc[0]/+cc[1]||aP}}function D(cd,ce,cc,i){if(!ce){return}cd.addEventListener?cd.addEventListener(ce,cc,!!i):cd.attachEvent("on"+ce,cc)}function a5(i,cc){if(i>cc.max){i=cc.max}else{if(i<cc.min){i=cc.min}}return i}function aD(i,ck,ch,cf,ce,cd,cc){var cg,cj,ci;if(cc==="horizontal"){cj=i.thumbwidth;ci=cd.width()}else{cj=i.thumbheight;ci=cd.height()}if((cj+i.margin)*(ch+1)>=(ci-cf)){if(cc==="horizontal"){cg=-ce.position().left}else{cg=-ce.position().top}}else{if((cj+i.margin)*(ch)<=Math.abs(cf)){if(cc==="horizontal"){cg=-ce.position().left+ci-(cj+i.margin)}else{cg=-ce.position().top+ci-(cj+i.margin)}}else{cg=cf}}cg=a5(cg,ck);return cg||0}function aj(i){return !!i.getAttribute("disabled")}function ad(cc,i){if(i){return{disabled:cc}}else{return{tabindex:cc*-1+"",disabled:cc}}}function a(cc,i){D(cc,"keyup",function(cd){aj(cc)||cd.keyCode==13&&i.call(cc,cd)})}function bL(cc,i){D(cc,"focus",cc.onfocusin=function(cd){i.call(cc,cd)},true)}function O(cc,i){cc.preventDefault?cc.preventDefault():(cc.returnValue=false);i&&cc.stopPropagation&&cc.stopPropagation()}function aE(cd,cc){var i=/iP(ad|hone|od)/i.test(bo.navigator.userAgent);if(i&&cc==="touchend"){cd.on("touchend",function(ce){bw.trigger("mouseup",ce)})}cd.on(cc,function(ce){O(ce,true);return false})}function ay(i){return i?">":"<"}var aS=(function(){function cd(ch,ce,cg){var cf=ce/cg;if(cf<=1){ch.parent().removeClass(au);ch.parent().addClass(aY)}else{ch.parent().removeClass(aY);ch.parent().addClass(au)}}function i(cf,cg,ch){var ce=ch;if(!cf.attr(ce)&&cf.attr(ce)!==aP){cf.attr(ce,cg)}if(cf.find("["+ce+"]").length){cf.find("["+ce+"]").each(function(){bV(this).attr(ce,cg)})}}function cc(cf,ce,ci){var cg=false,ch;cf.showCaption===ci||cf.showCaption===true?ch=true:ch=false;if(!ce){return false}if(cf.caption&&ch){cg=true}return cg}return{setRatio:cd,setThumbAttr:i,isExpectedCaption:cc}}(aS||{},jQuery));function A(ce,cd){var cc=ce.data(),i=Math.round(cd.pos),cf=function(){if(cc&&cc.sliding){cc.sliding=false}(cd.onEnd||g)()};if(typeof cd.overPos!=="undefined"&&cd.overPos!==cd.pos){i=cd.overPos}var cg=bV.extend(b2(i,cd.direction),cd.width&&{width:cd.width},cd.height&&{height:cd.height});if(cc&&cc.sliding){cc.sliding=true}if(aA){ce.css(bV.extend(b6(cd.time),cg));if(cd.time>10){X(ce,"transform",cf,cd.time)}else{cf()}}else{ce.stop().animate(cg,cd.time,u,cf)}}function aq(ck,cj,cc,cm,ce,i){var ch=typeof i!=="undefined";if(!ch){ce.push(arguments);Array.prototype.push.call(arguments,ce.length);if(ce.length>1){return}}ck=ck||bV(ck);cj=cj||bV(cj);var ci=ck[0],cg=cj[0],cf=cm.method==="crossfade",cl=function(){if(!cl.done){cl.done=true;var cn=(ch||ce.shift())&&ce.shift();cn&&aq.apply(this,cn);(cm.onEnd||g)(!!cn)}},cd=cm.time/(i||1);cc.removeClass(n+" "+al);ck.stop().addClass(n);cj.stop().addClass(al);cf&&cg&&ck.fadeTo(0,0);ck.fadeTo(cf?cd:0,1,cf&&cl);cj.fadeTo(cd,0,cl);(ci&&cf)||cg||cl()}var G,b5,e,j,bD;function bn(i){var cc=(i.touches||[])[0]||i;i._x=cc.pageX||cc.originalEvent.pageX;i._y=cc.clientY||cc.originalEvent.clientY;i._now=bV.now()}function aI(cr,cg){var cc=cr[0],cj={},i,cl,cf,cn,cs,cd,ce,co,ch;function cq(ct){cf=bV(ct.target);cj.checked=cd=ce=ch=false;if(i||cj.flow||(ct.touches&&ct.touches.length>1)||ct.which>1||(G&&G.type!==ct.type&&e)||(cd=cg.select&&cf.is(cg.select,cc))){return cd}cs=ct.type==="touchstart";ce=cf.is("a, a *",cc);cn=cj.control;co=(cj.noMove||cj.noSwipe||cn)?16:!cj.snap?4:0;bn(ct);cl=G=ct;b5=ct.type.replace(/down|start/,"move").replace(/Down/,"Move");(cg.onStart||g).call(cc,ct,{control:cn,$target:cf});i=cj.flow=true;if(!cs||cj.go){O(ct)}}function ck(cx){if((cx.touches&&cx.touches.length>1)||(aZ&&!cx.isPrimary)||b5!==cx.type||!i){i&&ci();(cg.onTouchEnd||g)();return}bn(cx);var cy=Math.abs(cx._x-cl._x),cu=Math.abs(cx._y-cl._y),cw=cy-cu,cv=(cj.go||cj.x||cw>=0)&&!cj.noSwipe,ct=cw<0;if(cs&&!cj.checked){if(i=cv){O(cx)}}else{O(cx);(cg.onMove||g).call(cc,cx,{touch:cs})}if(!ch&&Math.sqrt(Math.pow(cy,2)+Math.pow(cu,2))>co){ch=true}cj.checked=cj.checked||cv||ct}function ci(cu){(cg.onTouchEnd||g)();var ct=i;cj.control=i=false;if(ct){cj.flow=false}if(!ct||(ce&&!cj.checked)){return}cu&&O(cu);e=true;clearTimeout(j);j=setTimeout(function(){e=false},1000);(cg.onEnd||g).call(cc,{moved:ch,$target:cf,control:cn,touch:cs,startEvent:cl,aborted:!cu||cu.type==="MSPointerCancel"})}function cm(){if(cj.flow){return}cj.flow=true}function cp(){if(!cj.flow){return}cj.flow=false}if(aZ){D(cc,"MSPointerDown",cq);D(k,"MSPointerMove",ck);D(k,"MSPointerCancel",ci);D(k,"MSPointerUp",ci)}else{D(cc,"touchstart",cq);D(cc,"touchmove",ck);D(cc,"touchend",ci);D(k,"touchstart",cm);D(k,"touchend",cp);D(k,"touchcancel",cp);bf.on("scroll",cp);cr.on("mousedown pointerdown",cq);bw.on("mousemove pointermove",ck).on("mouseup pointerup",ci)}if(ap.touch){bD="a"}else{bD="div"}cr.on("click",bD,function(ct){cj.checked&&O(ct)});return cj}function ao(cz,cd){var cc=cz[0],ce=cz.data(),cm={},cw,cf,cx,cj,ch,cy,co,cg,cr,ct,cp,cq,i,cv,ci,cn;function cs(cA,cB){cn=true;cw=cf=(cq==="vertical")?cA._y:cA._x;co=cA._now;cy=[[co,cw]];cx=cj=cm.noMove||cB?0:a1(cz,(cd.getPos||g)());(cd.onStart||g).call(cc,cA)}function cu(cB,cA){cr=cm.min;ct=cm.max;cp=cm.snap,cq=cm.direction||"horizontal",cz.navdir=cq;i=cB.altKey;cn=ci=false;cv=cA.control;if(!cv&&!ce.sliding){cs(cB)}}function cl(cB,cA){if(!cm.noSwipe){if(!cn){cs(cB)}cf=(cq==="vertical")?cB._y:cB._x;cy.push([cB._now,cf]);cj=cx-(cw-cf);ch=bp(cj,cr,ct,cq);if(cj<=cr){cj=aF(cj,cr)}else{if(cj>=ct){cj=aF(cj,ct)}}if(!cm.noMove){cz.css(b2(cj,cq));if(!ci){ci=true;cA.touch||aZ||cz.addClass(bR)}(cd.onMove||g).call(cc,cB,{pos:cj,edge:ch})}}}function ck(cJ){if(cm.noSwipe&&cJ.moved){return}if(!cn){cs(cJ.startEvent,true)}cJ.touch||aZ||cz.removeClass(bR);cg=bV.now();var cG=cg-b8,cK,cP,cQ,cS=null,cA,cE,cN,cD,cF,cI=ba,cO,cH=cd.friction;for(var cC=cy.length-1;cC>=0;cC--){cK=cy[cC][0];cP=Math.abs(cK-cG);if(cS===null||cP<cQ){cS=cK;cA=cy[cC][1]}else{if(cS===cG||cP>cQ){break}}cQ=cP}cD=bb(cj,cr,ct);var cT=cA-cf,cR=cT>=0,cL=cg-cS,cB=cL>b8,cM=!cB&&cj!==cx&&cD===cj;if(cp){cD=bb(Math[cM?(cR?"floor":"ceil"):"round"](cj/cp)*cp,cr,ct);cr=ct=cD}if(cM&&(cp||cD===cj)){cO=-(cT/cL);cI*=bb(Math.abs(cO),cd.timeLow,cd.timeHigh);cE=Math.round(cj+cO*cI/cH);if(!cp){cD=cE}if(!cR&&cE>ct||cR&&cE<cr){cN=cR?cr:ct;cF=cE-cN;if(!cp){cD=cN}cF=bb(cD+cF*0.03,cN-50,cN+50);cI=Math.abs((cj-cF)/(cO/cH))}}cI*=i?10:1;(cd.onEnd||g).call(cc,bV.extend(cJ,{moved:cJ.moved||cB&&cp,pos:cj,newPos:cD,overPos:cF,time:cI,dir:cq}))}cm=bV.extend(aI(cd.$wrap,bV.extend({},cd,{onStart:cu,onMove:cl,onEnd:ck})),cm);return cm}function o(ce,cd){var cg=ce[0],ch,cf,i,cc={prevent:{}};D(cg,y,function(co){var cl=co.wheelDeltaY||-1*co.deltaY||0,cn=co.wheelDeltaX||-1*co.deltaX||0,ck=Math.abs(cn)&&!Math.abs(cl),cm=ay(cn<0),cp=cf===cm,ci=bV.now(),cj=ci-i<b8;cf=cm;i=ci;if(!ck||!cc.ok||cc.prevent[cm]&&!ch){return}else{O(co,true);if(ch&&cp&&cj){return}}if(cd.shift){ch=true;clearTimeout(cc.t);cc.t=setTimeout(function(){ch=false},bY)}(cd.onEnd||g)(co,cd.shift?cm:cn)});return cc}jQuery.Fotorama=function(d6,c3){R=bV("html");I=bV("body");var cg=this,cC=bV.now(),cQ=ag+cC,eu=d6[0],dP,cY=1,cR=d6.data(),c1,de=bV("<style></style>"),c9=bV(ab(bs)),dk=d6.find(aT(ae)),cf=dk.find(aT(a7)),dY=cf[0],cl=d6.find(aT(B)),c8=bV(),dW=d6.find(aT(bc)),da=d6.find(aT(r)),cU=d6.find(aT(aK)),dU=d6.find(aT(bq)),dO=dU.find(aT(bO)),cF=dO.find(aT(aH)),dA,cB=bV(),cW=bV(),dS=cl.data(),cX=cF.data(),c7=d6.find(aT(cb)),eg=d6.find(aT(b0)),dX=d6.find(aT(H)),dM=d6.find(aT(aJ)),dD=dM[0],cH=bV(ab(T)),dt=d6.find(aT(w)),d1=dt[0],eb=d6.find(aT(ca)),dg,eo=false,dF,ea,c2,ed,dw,d4,cN,cK,dx,dj,cq,c0,d8,c4,d2,cv,ch,ej,ds,cu,ec,dH,dE,d0={},en={},dG,d5={},cG={},dy={},ef={},cs,cT,ee,cj,el,cd={},er={},dZ,c6,dz,dr,d3=0,cI=[];dk[bu]=bV('<div class="'+x+'"></div>');dk[bl]=bV(bV.Fotorama.jst.thumb());dk[b7]=bV(bV.Fotorama.jst.dots());cd[bu]=[];cd[bl]=[];cd[b7]=[];er[bu]={};dk.addClass(aA?aX:ah);cR.fotorama=this;function ep(){bV.each(dP,function(ey,eA){if(!eA.i){eA.i=cY++;var ez=at(eA.video,true);if(ez){var ex={};eA.video=ez;if(!eA.img&&!eA.thumb){ex=aQ(eA,dP,cg)}else{eA.thumbsReady=true}v(dP,{img:ex.img,thumb:ex.thumb},eA.i,cg)}}})}function df(ex){return dE[ex]}function i(){if(cf!==aP){if(c3.navdir=="vertical"){var ex=c3.thumbwidth+c3.thumbmargin;cf.css("left",ex);da.css("right",ex);dM.css("right",ex);dk.css("width",dk.css("width")+ex);cl.css("max-width",dk.width()-ex)}else{cf.css("left","");da.css("right","");dM.css("right","");dk.css("width",dk.css("width")+ex);cl.css("max-width","")}}}function ek(eB){var eC="keydown."+ag,eD=ag+cC,ex="keydown."+eD,eA="keyup."+eD,ey="resize."+eD+" orientationchange."+eD,ez;if(eB){bw.on(ex,function(eG){var eF,eE;if(dg&&eG.keyCode===27){eF=true;cO(dg,true,true)}else{if(cg.fullScreen||(c3.keyboard&&!cg.index)){if(eG.keyCode===27){eF=true;cg.cancelFullScreen()}else{if((eG.shiftKey&&eG.keyCode===32&&df("space"))||(eG.keyCode===37&&df("left"))||(eG.keyCode===38&&df("up")&&bV(":focus").attr("data-gallery-role"))){cg.longPress.progress();eE="<"}else{if((eG.keyCode===32&&df("space"))||(eG.keyCode===39&&df("right"))||(eG.keyCode===40&&df("down")&&bV(":focus").attr("data-gallery-role"))){cg.longPress.progress();eE=">"}else{if(eG.keyCode===36&&df("home")){cg.longPress.progress();eE="<<"}else{if(eG.keyCode===35&&df("end")){cg.longPress.progress();eE=">>"}}}}}}}(eF||eE)&&O(eG);ez={index:eE,slow:eG.altKey,user:true};eE&&(cg.longPress.inProgress?cg.showWhileLongPress(ez):cg.show(ez))});if(eB){bw.on(eA,function(eE){if(cg.longPress.inProgress){cg.showEndLongPress({user:true})}cg.longPress.reset()})}if(!cg.index){bw.off(eC).on(eC,"textarea, input, select",function(eE){!I.hasClass(bH)&&eE.stopPropagation()})}bf.on(ey,cg.resize)}else{bw.off(ex);bf.off(ey)}}function dd(ex){if(ex===dd.f){return}if(ex){d6.addClass(ag+" "+cQ).before(c9).before(de);C(cg)}else{c9.detach();de.detach();d6.html(cR.urtext).removeClass(cQ);av(cg)}ek(ex);dd.f=ex}function dn(){dP=cg.data=dP||bG(c3.data)||bI(d6);c1=cg.size=dP.length;eq.ok&&c3.shuffle&&aC(dP);ep();eo=cn(eo);c1&&dd(true)}function em(){var ex=c1<2||dg;d5.noMove=ex||cv;d5.noSwipe=ex||!c3.swipe;!cu&&cl.toggleClass(aB,!c3.click&&!d5.noMove&&!d5.noSwipe);aZ&&dk.toggleClass(U,!d5.noSwipe)}function dq(ex){if(ex===true){ex=""}c3.autoplay=Math.max(+ex||bQ,ds*1.5)}function db(ex){if(ex.navarrows&&ex.nav==="thumbs"){eg.show();dX.show()}else{eg.hide();dX.hide()}}function ck(ex,ey){return Math.floor(dk.width()/(ey.thumbwidth+ey.thumbmargin))}function dQ(){if(!c3.nav||c3.nav==="dots"){c3.navdir="horizontal"}cg.options=c3=bA(c3);b4=ck(dk,c3);cv=(c3.transition==="crossfade"||c3.transition==="dissolve");dj=c3.loop&&(c1>2||(cv&&(!cu||cu!=="slide")));ds=+c3.transitionduration||ba;dH=c3.direction==="rtl";dE=bV.extend({},c3.keyboard&&p,c3.keyboard);db(c3);var ey={add:[],remove:[]};function ex(ez,eA){ey[ez?"add":"remove"].push(eA)}if(c1>1){cq=c3.nav;d8=c3.navposition==="top";ey.remove.push(a9);cU.toggle(c3.arrows)}else{cq=false;cU.hide()}dh();cJ();ev();if(c3.autoplay){dq(c3.autoplay)}ch=m(c3.thumbwidth)||L;ej=m(c3.thumbheight)||L;cG.ok=ef.ok=c3.trackpad&&!bh;em();dL(c3,[en]);c0=cq==="thumbs";if(dU.filter(":hidden")&&!!cq){dU.show()}if(c0){dl(c1,"navThumb");dA=cW;dr=bl;an(de,bV.Fotorama.jst.style({w:ch,h:ej,b:c3.thumbborderwidth,m:c3.thumbmargin,s:cC,q:!aN}));dO.addClass(ai).removeClass(bW)}else{if(cq==="dots"){dl(c1,"navDot");dA=cB;dr=b7;dO.addClass(bW).removeClass(ai)}else{dU.hide();cq=false;dO.removeClass(ai+" "+bW)}}if(cq){if(d8){dU.insertBefore(cf)}else{dU.insertAfter(cf)}cz.nav=false;cz(dA,cF,"nav")}c4=c3.allowfullscreen;if(c4){dM.prependTo(cf);d2=s&&c4==="native";aE(dM,"touchend")}else{dM.detach();d2=false}ex(cv,ar);ex(!cv,aw);ex(!c3.captions,bN);ex(dH,a0);ex(c3.arrows,f);ec=c3.shadows&&!bh;ex(!ec,aM);dk.addClass(ey.add.join(" ")).removeClass(ey.remove.join(" "));d0=bV.extend({},c3);i()}function cZ(ex){return ex<0?(c1+(ex%c1))%c1:ex>=c1?ex%c1:ex}function cn(ex){return bb(ex,0,c1-1)}function du(ex){return dj?cZ(ex):cn(ex)}function dB(ex){return ex>0||dj?ex-1:false}function ci(ex){return ex<c1-1||dj?ex+1:false}function d9(){d5.min=dj?-Infinity:-a8(c1-1,en.w,c3.margin,c2);d5.max=dj?Infinity:-a8(0,en.w,c3.margin,c2);d5.snap=en.w+c3.margin}function c5(){var ex=(c3.navdir==="vertical");var ez=ex?cF.height():cF.width();var ey=ex?en.h:en.nw;dy.min=Math.min(0,ey-ez);dy.max=0;dy.direction=c3.navdir;cF.toggleClass(aB,!(dy.noMove=dy.min===dy.max))}function dm(ey,eA,ez){if(typeof ey==="number"){ey=new Array(ey);var ex=true}return bV.each(ey,function(eD,eB){if(ex){eB=eD}if(typeof eB==="number"){var eF=dP[cZ(eB)];if(eF){var eC="$"+eA+"Frame",eE=eF[eC];ez.call(this,eD,eB,eF,eE,eC,eE&&eE.data())}}})}function cc(eA,ex,ez,ey){if(!dG||(dG==="*"&&ey===dx)){eA=t(c3.width)||t(eA)||bk;ex=t(c3.height)||t(ex)||bE;cg.resize({width:eA,ratio:c3.ratio||ez||eA/ex},0,ey!==dx&&"*")}}function cx(ex,ey,eA,ez){dm(ex,ey,function(eM,eE,eD,eC,eR,eB){if(!eC){return}var eN=cg.fullScreen&&!eB.$full&&ey==="stage";if(eB.$img&&!ez&&!eN){return}var eS=new Image(),eG=bV(eS),eO=eG.data();eB[eN?"$full":"$img"]=eG;var eJ=ey==="stage"?(eN?"full":"img"):"thumb",eF=eD[eJ],eP=eN?eD.img:eD[ey==="stage"?"thumb":"img"];if(ey==="navThumb"){eC=eB.$wrap}function eH(eT){var eU=cZ(eE);dc(eT,{index:eU,src:eF,frame:dP[eU]})}function eK(){eG.remove();bV.Fotorama.cache[eF]="error";if((!eD.html||ey!=="stage")&&eP&&eP!==eF){eD[eJ]=eF=eP;eB.$full=null;cx([eE],ey,eA,true)}else{if(eF&&!eD.html&&!eN){eC.trigger("f:error").removeClass(bM).addClass(bP);eH("error")}else{if(ey==="stage"){eC.trigger("f:load").removeClass(bM+" "+bP).addClass(c);eH("load");cc()}}eB.state="error";if(c1>1&&dP[eE]===eD&&!eD.html&&!eD.deleted&&!eD.video&&!eN){eD.deleted=true;cg.splice(eE,1)}}}function eL(){bV.Fotorama.measures[eF]=eO.measures=bV.Fotorama.measures[eF]||{width:eS.width,height:eS.height,ratio:eS.width/eS.height};cc(eO.measures.width,eO.measures.height,eO.measures.ratio,eE);eG.off("load error").addClass(""+(eN?Y:J)).attr("aria-hidden","false").prependTo(eC);if(eC.hasClass(x)&&!eC.hasClass(af)){eC.attr("href",eG.attr("src"))}V(eG,(bV.isFunction(eA)?eA():eA)||en);bV.Fotorama.cache[eF]=eB.state="loaded";setTimeout(function(){eC.trigger("f:load").removeClass(bM+" "+bP).addClass(c+" "+(eN?b3:bg));if(ey==="stage"){eH("load")}else{if(eD.thumbratio===bF||!eD.thumbratio&&c3.thumbratio===bF){eD.thumbratio=eO.measures.ratio;dV()}}},0)}if(!eF){eK();return}function eI(){var eT=10;bX(function(){return !c6||!eT--&&!bh},function(){eL()})}if(!bV.Fotorama.cache[eF]){bV.Fotorama.cache[eF]="*";eG.on("load",eI).on("error",eK)}else{(function eQ(){if(bV.Fotorama.cache[eF]==="error"){eK()}else{if(bV.Fotorama.cache[eF]==="loaded"){setTimeout(eI,0)}else{setTimeout(eQ,100)}}})()}eB.state="";eS.src=eF;if(eB.data.caption){eS.alt=eB.data.caption||""}if(eB.data.full){bV(eS).data("original",eB.data.full)}if(aS.isExpectedCaption(eD,c3.showcaption)){bV(eS).attr("aria-labelledby",eD.labelledby)}})}function cy(){var ex=dF[bu];if(ex&&!ex.data().state){eb.addClass(Z);ex.on("f:load f:error",function(){ex.off("f:load f:error");eb.removeClass(Z)})}}function cL(ex){a(ex,dJ);bL(ex,function(){setTimeout(function(){bU(dO)},0);dT({time:ds,guessIndex:bV(this).data().eq,minMax:dy})})}function dl(ex,ey){dm(ex,ey,function(eB,ez,eG,eD,eA,eC){if(eD){return}eD=eG[eA]=dk[eA].clone();eC=eD.data();eC.data=eG;var eF=eD[0],eE="labelledby"+bV.now();if(ey==="stage"){if(eG.html){bV('<div class="'+bd+'"></div>').append(eG._html?bV(eG.html).removeAttr("id").html(eG._html):eG.html).appendTo(eD)}if(eG.id){eE=eG.id||eE}eG.labelledby=eE;if(aS.isExpectedCaption(eG,c3.showcaption)){bV(bV.Fotorama.jst.frameCaption({caption:eG.caption,labelledby:eE})).appendTo(eD)}eG.video&&eD.addClass(l).append(cH.clone());bL(eF,function(){setTimeout(function(){bU(cf)},0);cm({index:eC.eq,user:true})});c8=c8.add(eD)}else{if(ey==="navDot"){cL(eF);cB=cB.add(eD)}else{if(ey==="navThumb"){cL(eF);eC.$wrap=eD.children(":first");cW=cW.add(eD);if(eG.video){eC.$wrap.append(cH.clone())}}}}})}function cM(ey,ex){return ey&&ey.length&&V(ey,ex)}function di(ex){dm(ex,"stage",function(eB,ez,eE,eD,eA,eC){if(!eD){return}var ey=cZ(ez);eC.eq=ey;er[bu][ey]=eD.css(bV.extend({left:cv?0:a8(ez,en.w,c3.margin,c2)},cv&&b6(0)));if(be(eD[0])){eD.appendTo(cl);cO(eE.$video)}cM(eC.$img,en);cM(eC.$full,en);if(eD.hasClass(x)&&!(eD.attr("aria-hidden")==="false"&&eD.hasClass(a4))){eD.attr("aria-hidden","true")}})}function dp(eB,ex){var ey,ez,eA;if(cq!=="thumbs"||isNaN(eB)){return}ey=-eB;ez=-eB+en.nw;if(c3.navdir==="vertical"){eB=eB-c3.thumbheight;ez=-eB+en.h}cW.each(function(){var eH=bV(this),eD=eH.data(),eC=eD.eq,eG=function(){return{h:ej,w:eD.w}},eF=eG(),eE=c3.navdir==="vertical"?eD.t>ez:eD.l>ez;eF.w=eD.w;if(eD.l+eD.w<ey||eE||cM(eD.$img,eF)){return}ex&&cx([eC],"navThumb",eG)})}function cz(ex,eC,ey){if(!cz[ey]){var eB=ey==="nav"&&c0,eA=0,ez=0;eC.append(ex.filter(function(){var eH,eG=bV(this),eE=eG.data();for(var eF=0,eD=dP.length;eF<eD;eF++){if(eE.data===dP[eF]){eH=true;eE.eq=eF;break}}return eH||eG.remove()&&false}).sort(function(eE,eD){return bV(eE).data().eq-bV(eD).data().eq}).each(function(){var eE=bV(this),eD=eE.data();aS.setThumbAttr(eE,eD.data.caption,"aria-label")}).each(function(){if(!eB){return}var eF=bV(this),eE=eF.data(),eG=Math.round(ej*eE.data.thumbratio)||ch,eD=Math.round(ch/eE.data.thumbratio)||ej;eE.t=ez;eE.h=eD;eE.l=eA;eE.w=eG;eF.css({width:eG});ez+=eD+c3.thumbmargin;eA+=eG+c3.thumbmargin}));cz[ey]=true}}function eh(ex){return ex-d3>en.w/3}function cE(ex){return !dj&&(!(eo+ex)||!(eo-c1+ex))&&!dg}function dh(){var ey=cE(0),ex=cE(1);dW.toggleClass(F,ey).attr(ad(ey,false));da.toggleClass(F,ex).attr(ad(ex,false))}function ev(){var ex=false,ey=false;if(c3.navtype==="thumbs"&&!c3.loop){(eo==0)?ex=true:ex=false;(eo==c3.data.length-1)?ey=true:ey=false}if(c3.navtype==="slides"){var ez=aa(cF,c3.navdir);ez>=dy.max?ex=true:ex=false;ez<=dy.min?ey=true:ey=false}eg.toggleClass(F,ex).attr(ad(ex,true));dX.toggleClass(F,ey).attr(ad(ey,true))}function cJ(){if(cG.ok){cG.prevent={"<":cE(0),">":cE(1)}}}function dI(eD){var eA=eD.data(),eC,eB,ez,ex;if(c0){eC=eA.l;eB=eA.t;ez=eA.w;ex=eA.h}else{eC=eD.position().left;ez=eD.width()}var ey={c:eC+ez/2,min:-eC+c3.thumbmargin*10,max:-eC+en.w-ez-c3.thumbmargin*10};var eE={c:eB+ex/2,min:-eB+c3.thumbmargin*10,max:-eB+en.h-ex-c3.thumbmargin*10};return c3.navdir==="vertical"?eE:ey}function d7(ey){var ex=dF[dr].data();A(c7,{time:ey*1.2,pos:(c3.navdir==="vertical"?ex.t:ex.l),width:ex.w,height:ex.h,direction:c3.navdir})}function dT(eH){var eB=dP[eH.guessIndex][dr],ez=c3.navtype;var eD,ex,eA,eG,eC,ey,eE,eF;if(eB){if(ez==="thumbs"){eD=dy.min!==dy.max;eA=eH.minMax||eD&&dI(dF[dr]);eG=eD&&(eH.keep&&dT.t?dT.l:bb((eH.coo||en.nw/2)-dI(eB).c,eA.min,eA.max));eC=eD&&(eH.keep&&dT.l?dT.l:bb((eH.coo||en.nw/2)-dI(eB).c,eA.min,eA.max));ey=(c3.navdir==="vertical"?eG:eC);eE=eD&&bb(ey,dy.min,dy.max)||0;ex=eH.time*1.1;A(cF,{time:ex,pos:eE,direction:c3.navdir,onEnd:function(){dp(eE,true);ev()}});co(dO,bp(eE,dy.min,dy.max,c3.navdir));dT.l=ey}else{eF=aa(cF,c3.navdir);ex=eH.time*1.11;eE=aD(c3,dy,eH.guessIndex,eF,eB,dU,c3.navdir);A(cF,{time:ex,pos:eE,direction:c3.navdir,onEnd:function(){dp(eE,true);ev()}});co(dO,bp(eE,dy.min,dy.max,c3.navdir))}}}function cS(){dN(dr);cd[dr].push(dF[dr].addClass(a4).attr("data-active",true))}function dN(ey){var ex=cd[ey];while(ex.length){ex.shift().removeClass(a4).attr("data-active",false)}}function ce(ey){var ex=er[ey];bV.each(ea,function(eA,ez){delete ex[cZ(ez)]});bV.each(ex,function(ez,eA){delete ex[ez];eA.detach()})}function dC(ey){c2=ed=eo;var ex=dF[bu];if(ex){dN(bu);cd[bu].push(ex.addClass(a4).attr("data-active",true));if(ex.hasClass(x)){ex.attr("aria-hidden","false")}ey||cg.showStage.onEnd(true);a1(cl,0,true);ce(bu);di(ea);d9();c5();a(cl[0],function(){if(!d6.hasClass(M)){cg.requestFullScreen();dM.focus()}})}}function dL(ey,ex){if(!ey){return}bV.each(ex,function(ez,eA){if(!eA){return}bV.extend(eA,{width:ey.width||eA.width,height:ey.height,minwidth:ey.minwidth,maxwidth:ey.maxwidth,minheight:ey.minheight,maxheight:ey.maxheight,ratio:bm(ey.ratio)})})}function dc(ey,ex){d6.trigger(ag+":"+ey,[cg,ex])}function dR(){clearTimeout(cr.t);c6=1;if(c3.stopautoplayontouch){cg.stopAutoplay()}else{cj=true}}function cr(){if(!c6){return}if(!c3.stopautoplayontouch){cw();es()}cr.t=setTimeout(function(){c6=0},ba+b8)}function cw(){cj=!!(dg||el)}function es(){clearTimeout(es.t);bX.stop(es.w);if(!c3.autoplay||cj){if(cg.autoplay){cg.autoplay=false;dc("stopautoplay")}return}if(!cg.autoplay){cg.autoplay=true;dc("startautoplay")}var ey=eo;var ex=dF[bu].data();es.w=bX(function(){return ex.state||ey!==eo},function(){es.t=setTimeout(function(){if(cj||ey!==eo){return}var ez=cK,eA=dP[ez][bu].data();es.w=bX(function(){return eA.state||ez!==cK},function(){if(cj||ez!==cK){return}cg.show(dj?ay(!dH):cK)})},c3.autoplay)})}cg.startAutoplay=function(ex){if(cg.autoplay){return this}cj=el=false;dq(ex||c3.autoplay);es();return this};cg.stopAutoplay=function(){if(cg.autoplay){cj=el=true;es()}return this};cg.showSlide=function(ez){var eA=aa(cF,c3.navdir),eC,eB=500*1.1,ey=c3.navdir==="horizontal"?c3.thumbwidth:c3.thumbheight,ex=function(){ev()};if(ez==="next"){eC=eA-(ey+c3.margin)*b4}if(ez==="prev"){eC=eA+(ey+c3.margin)*b4}eC=a5(eC,dy);dp(eC,true);A(cF,{time:eB,pos:eC,direction:c3.navdir,onEnd:ex})};cg.showWhileLongPress=function(eA){if(cg.longPress.singlePressInProgress){return}var ez=dK(eA);ew(ez);var eB=cA(eA)/50;var ey=dF;cg.activeFrame=dF=dP[eo];var ex=ey===dF&&!eA.user;cg.showNav(ex,eA,eB);return this};cg.showEndLongPress=function(eA){if(cg.longPress.singlePressInProgress){return}var ez=dK(eA);ew(ez);var eB=cA(eA)/50;var ey=dF;cg.activeFrame=dF=dP[eo];var ex=ey===dF&&!eA.user;cg.showStage(ex,eA,eB);ee=typeof dw!=="undefined"&&dw!==eo;dw=eo;return this};function dK(ey){var ex;if(typeof ey!=="object"){ex=ey;ey={}}else{ex=ey.index}ex=ex===">"?ed+1:ex==="<"?ed-1:ex==="<<"?0:ex===">>"?c1-1:ex;ex=isNaN(ex)?aP:ex;ex=typeof ex==="undefined"?eo||0:ex;return ex}function ew(ex){cg.activeIndex=eo=du(ex);d4=dB(eo);cN=ci(eo);cK=cZ(eo+(dH?-1:1));ea=[eo,d4,cN];ed=dj?ex:eo}function cA(ey){var ex=Math.abs(dw-ed),ez=bj(ey.time,function(){return Math.min(ds*(1+(ex-1)/12),ds*2)});if(ey.slow){ez*=10}return ez}cg.showStage=function(ey,eA,eD){cO(dg,dF.i!==dP[cZ(c2)].i);dl(ea,"stage");di(bh?[ed]:[ed,dB(ed),ci(ed)]);cD("go",true);ey||dc("show",{user:eA.user,time:eD});cj=true;var eC=eA.overPos;var ez=cg.showStage.onEnd=function(eE){if(ez.ok){return}ez.ok=true;eE||dC(true);if(!ey){dc("showend",{user:eA.user})}if(!eE&&cu&&cu!==c3.transition){cg.setOptions({transition:cu});cu=false;return}cy();cx(ea,"stage");cD("go",false);cJ();ei();cw();es();if(cg.fullScreen){dF[bu].find("."+Y).attr("aria-hidden",false);dF[bu].find("."+J).attr("aria-hidden",true)}else{dF[bu].find("."+Y).attr("aria-hidden",true);dF[bu].find("."+J).attr("aria-hidden",false)}};if(!cv){A(cl,{pos:-a8(ed,en.w,c3.margin,c2),overPos:eC,time:eD,onEnd:ez})}else{var ex=dF[bu],eB=dP[dw]&&eo!==dw?dP[dw][bu]:null;aq(ex,eB,c8,{time:eD,method:c3.transition,onEnd:ez},cI)}dh()};cg.showNav=function(ey,ez,eA){ev();if(cq){cS();var ex=cn(eo+bb(ed-dw,-1,1));dT({time:eA,coo:ex!==eo&&ez.coo,guessIndex:typeof ez.coo!=="undefined"?ex:eo,keep:ey});if(c0){d7(eA)}}};cg.show=function(eA){cg.longPress.singlePressInProgress=true;var ez=dK(eA);ew(ez);var eB=cA(eA);var ey=dF;cg.activeFrame=dF=dP[eo];var ex=ey===dF&&!eA.user;cg.showStage(ex,eA,eB);cg.showNav(ex,eA,eB);ee=typeof dw!=="undefined"&&dw!==eo;dw=eo;cg.longPress.singlePressInProgress=false;return this};cg.requestFullScreen=function(){if(c4&&!cg.fullScreen){var ex=bV((cg.activeFrame||{}).$stageFrame||{}).hasClass("fotorama-video-container");if(ex){return}cs=bf.scrollTop();cT=bf.scrollLeft();bU(bf);cD("x",true);dZ=bV.extend({},en);d6.addClass(M).appendTo(I.addClass(bH));R.addClass(bH);cO(dg,true,true);cg.fullScreen=true;if(d2){bB.request(eu)}cg.resize();cx(ea,"stage");cy();dc("fullscreenenter");if(!("ontouchstart" in bo)){dM.focus()}}return this};function cP(){if(cg.fullScreen){cg.fullScreen=false;if(s){bB.cancel(eu)}I.removeClass(bH);R.removeClass(bH);d6.removeClass(M).insertAfter(c9);en=bV.extend({},dZ);cO(dg,true,true);cD("x",false);cg.resize();cx(ea,"stage");bU(bf,cT,cs);dc("fullscreenexit")}}cg.cancelFullScreen=function(){if(d2&&bB.is()){bB.cancel(k)}else{cP()}return this};cg.toggleFullScreen=function(){return cg[(cg.fullScreen?"cancel":"request")+"FullScreen"]()};cg.resize=function(ez){if(!dP){return this}var eC=arguments[1]||0,ey=arguments[2];b4=ck(dk,c3);dL(!cg.fullScreen?bA(ez):{width:bV(bo).width(),maxwidth:null,minwidth:null,height:bV(bo).height(),maxheight:null,minheight:null},[en,ey||cg.fullScreen||c3]);var eB=en.width,ex=en.height,eA=en.ratio,eD=bf.height()-(cq?dO.height():0);if(t(eB)){dk.css({width:""});dk.css({height:""});cf.css({width:""});cf.css({height:""});cl.css({width:""});cl.css({height:""});dO.css({width:""});dO.css({height:""});dk.css({minWidth:en.minwidth||0,maxWidth:en.maxwidth||bK});if(cq==="dots"){dU.hide()}eB=en.W=en.w=dk.width();en.nw=cq&&d(c3.navwidth,eB)||eB;cl.css({width:en.w,marginLeft:(en.W-en.w)/2});ex=d(ex,eD);ex=ex||(eA&&eB/eA);if(ex){eB=Math.round(eB);ex=en.h=Math.round(bb(ex,d(en.minheight,eD),d(en.maxheight,eD)));cf.css({width:eB,height:ex});if(c3.navdir==="vertical"&&!cg.fullscreen){dO.width(c3.thumbwidth+c3.thumbmargin*2)}if(c3.navdir==="horizontal"&&!cg.fullscreen){dO.height(c3.thumbheight+c3.thumbmargin*2)}if(cq==="dots"){dO.width(eB).height("auto");dU.show()}if(c3.navdir==="vertical"&&cg.fullScreen){cf.css("height",bf.height())}if(c3.navdir==="horizontal"&&cg.fullScreen){cf.css("height",bf.height()-dO.height())}if(cq){switch(c3.navdir){case"vertical":dU.removeClass(bZ);dU.removeClass(ax);dU.addClass(b);dO.stop().animate({height:en.h,width:c3.thumbwidth},eC);break;case"list":dU.removeClass(b);dU.removeClass(bZ);dU.addClass(ax);break;default:dU.removeClass(b);dU.removeClass(ax);dU.addClass(bZ);dO.stop().animate({width:en.nw},eC);break}dC();dT({guessIndex:eo,time:eC,keep:true});if(c0&&cz.nav){d7(eC)}}dG=ey||true;eq.ok=true;eq()}}d3=cf.offset().left;i();return this};cg.setOptions=function(ex){bV.extend(c3,ex);dV();return this};cg.shuffle=function(){dP&&aC(dP)&&dV();return this};function co(ex,ey){if(ec){ex.removeClass(S+" "+aL);ex.removeClass(a2+" "+aR);ey&&!dg&&ex.addClass(ey.replace(/^|\s/g," "+bz+"--"))}}cg.longPress={threshold:1,count:0,thumbSlideTime:20,progress:function(){if(!this.inProgress){this.count++;this.inProgress=this.count>this.threshold}},end:function(){if(this.inProgress){this.isEnded=true}},reset:function(){this.count=0;this.inProgress=false;this.isEnded=false}};cg.destroy=function(){cg.cancelFullScreen();cg.stopAutoplay();dP=cg.data=null;dd();ea=[];ce(bu);dV.ok=false;return this};cg.playVideo=function(){var ez=dF,ex=ez.video,ey=eo;if(typeof ex==="object"&&ez.videoReady){d2&&cg.fullScreen&&cg.cancelFullScreen();bX(function(){return !bB.is()||ey!==eo},function(){if(ey===eo){ez.$video=ez.$video||bV(ab(bJ)).append(q(ex));ez.$video.appendTo(ez[bu]);dk.addClass(bt);dg=ez.$video;em();cU.blur();dM.blur();dc("loadvideo")}})}return this};cg.stopVideo=function(){cO(dg,true,true);return this};cg.spliceByIndex=function(ex,ey){ey.i=ex+1;ey.img&&bV.ajax({url:ey.img,type:"HEAD",success:function(){dP.splice(ex,1,ey);dV()}})};function cO(ex,ez,ey){if(ez){dk.removeClass(bt);dg=false;em()}if(ex&&ex!==dg){ex.remove();dc("unloadvideo")}if(ey){cw();es()}}function cp(ex){dk.toggleClass(P,ex)}function ei(ez){if(d5.flow){return}var ex=ez?ez.pageX:ei.x,ey=ex&&!cE(eh(ex))&&c3.click;if(ei.p!==ey&&cf.toggleClass(bC,ey)){ei.p=ey;ei.x=ex}}cf.on("mousemove",ei);function cm(ex){clearTimeout(cm.t);if(c3.clicktransition&&c3.clicktransition!==c3.transition){setTimeout(function(){var ey=c3.transition;cg.setOptions({transition:c3.clicktransition});cu=ey;cm.t=setTimeout(function(){cg.show(ex)},10)},0)}else{cg.show(ex)}}function ct(eA,ey){var ez=eA.target,ex=bV(ez);if(ex.hasClass(T)){cg.playVideo()}else{if(ez===dD){cg.toggleFullScreen()}else{if(dg){ez===d1&&cO(dg,true,true)}else{if(!d6.hasClass(M)){cg.requestFullScreen()}}}}O(eA,true)}function cD(ex,ey){d5[ex]=dy[ex]=ey}d5=ao(cl,{onStart:dR,onMove:function(ey,ex){co(cf,ex.edge)},onTouchEnd:cr,onEnd:function(ex){var ez;co(cf);ez=(aZ&&!dz||ex.touch)&&c3.arrows;if((ex.moved||(ez&&ex.pos!==ex.newPos&&!ex.control))&&ex.$target[0]!==dM[0]){var ey=by(ex.newPos,en.w,c3.margin,c2);cg.show({index:ey,time:cv?ds:ex.time,overPos:ex.overPos,user:true})}else{if(!ex.aborted&&!ex.control){ct(ex.startEvent,ez)}}},timeLow:1,timeHigh:1,friction:2,select:"."+a9+", ."+a9+" *",$wrap:cf,direction:"horizontal"});dy=ao(cF,{onStart:dR,onMove:function(ey,ex){co(dO,ex.edge)},onTouchEnd:cr,onEnd:function(ex){function ey(){dT.l=ex.newPos;cw();es();dp(ex.newPos,true);ev()}if(!ex.moved){var ez=ex.$target.closest("."+aG,cF)[0];ez&&dJ.call(ez,ex.startEvent)}else{if(ex.pos!==ex.newPos){cj=true;A(cF,{time:ex.time,pos:ex.newPos,overPos:ex.overPos,direction:c3.navdir,onEnd:ey});dp(ex.newPos);ec&&co(dO,bp(ex.newPos,dy.min,dy.max,ex.dir))}else{ey()}}},timeLow:0.5,timeHigh:2,friction:5,$wrap:dO,direction:c3.navdir});cG=o(cf,{shift:true,onEnd:function(ey,ex){dR();cr();cg.show({index:ex,slow:ey.altKey})}});ef=o(dO,{onEnd:function(ez,ey){dR();cr();var ex=a1(cF)+ey*0.25;cF.css(b2(bb(ex,dy.min,dy.max),c3.navdir));ec&&co(dO,bp(ex,dy.min,dy.max,c3.navdir));ef.prevent={"<":ex>=dy.max,">":ex<=dy.min};clearTimeout(ef.t);ef.t=setTimeout(function(){dT.l=ex;dp(ex,true)},b8);dp(ex)}});dk.hover(function(){setTimeout(function(){if(c6){return}cp(!(dz=true))},0)},function(){if(!dz){return}cp(!(dz=false))});function dJ(ey){var ex=bV(this).data().eq;if(c3.navtype==="thumbs"){cm({index:ex,slow:ey.altKey,user:true,coo:ey._x-dO.offset().left})}else{cm({index:ex,slow:ey.altKey,user:true})}}function et(ex){cm({index:cU.index(this)?">":"<",slow:ex.altKey,user:true})}z(cU,function(ex){O(ex);et.call(this,ex)},{onStart:function(){dR();d5.control=true},onTouchEnd:cr});z(eg,function(ex){O(ex);if(c3.navtype==="thumbs"){cg.show("<")}else{cg.showSlide("prev")}});z(dX,function(ex){O(ex);if(c3.navtype==="thumbs"){cg.show(">")}else{cg.showSlide("next")}});function dv(ex){bL(ex,function(){setTimeout(function(){bU(cf)},0);cp(false)})}cU.each(function(){a(this,function(ex){et.call(this,ex)});dv(this)});a(dD,function(){if(d6.hasClass(M)){cg.cancelFullScreen();cl.focus()}else{cg.requestFullScreen();dM.focus()}});dv(dD);function dV(){dn();dQ();if(!dV.i){dV.i=true;var ex=c3.startindex;eo=c2=ed=dw=dx=du(ex)||0}if(c1){if(cV()){return}if(dg){cO(dg,true)}ea=[];ce(bu);dV.ok=true;cg.show({index:eo,time:0});cg.resize()}else{cg.destroy()}}function cV(){if(!cV.f===dH){cV.f=dH;eo=c1-1-eo;cg.reverse();return true}}bV.each("load push pop shift unshift reverse sort splice".split(" "),function(ex,ey){cg[ey]=function(){dP=dP||[];if(ey!=="load"){Array.prototype[ey].apply(dP,arguments)}else{if(arguments[0]&&typeof arguments[0]==="object"&&arguments[0].length){dP=bG(arguments[0])}}dV();return cg}});function eq(){if(eq.ok){eq.ok=false;dc("ready")}}dV()};bV.fn.fotorama=function(i){return this.each(function(){var ce=this,cd=bV(this),cc=cd.data(),cf=cc.fotorama;if(!cf){bX(function(){return !W(ce)},function(){cc.urtext=cd.html();new bV.Fotorama(cd,bV.extend({},Q,bo.fotoramaDefaults,i,cc))})}else{cf.setOptions(i,true)}})};bV.Fotorama.instances=[];function b1(){bV.each(bV.Fotorama.instances,function(cc,i){i.index=cc})}function C(i){bV.Fotorama.instances.push(i);b1()}function av(i){bV.Fotorama.instances.splice(i.index,1);b1()}bV.Fotorama.cache={};bV.Fotorama.measures={};bV=bV||{};bV.Fotorama=bV.Fotorama||{};bV.Fotorama.jst=bV.Fotorama.jst||{};bV.Fotorama.jst.dots=function(cc){var i,ce="",cd=bx.escape;ce+='<div class="fotorama__nav__frame fotorama__nav__frame--dot" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__dot"></div>\r\n</div>';return ce};bV.Fotorama.jst.frameCaption=function(cc){var i,ce="",cd=bx.escape;ce+='<div class="fotorama__caption" aria-hidden="true">\r\n <div class="fotorama__caption__wrap" id="'+((i=(cc.labelledby))==null?"":i)+'">'+((i=(cc.caption))==null?"":i)+"</div>\r\n</div>\r\n";return ce};bV.Fotorama.jst.style=function(cc){var i,ce="",cd=bx.escape;ce+=".fotorama"+((i=(cc.s))==null?"":i)+" .fotorama__nav--thumbs .fotorama__nav__frame{\r\npadding:"+((i=(cc.m))==null?"":i)+"px;\r\nheight:"+((i=(cc.h))==null?"":i)+"px}\r\n.fotorama"+((i=(cc.s))==null?"":i)+" .fotorama__thumb-border{\r\nheight:"+((i=(cc.h))==null?"":i)+"px;\r\nborder-width:"+((i=(cc.b))==null?"":i)+"px;\r\nmargin-top:"+((i=(cc.m))==null?"":i)+"px}";return ce};bV.Fotorama.jst.thumb=function(cc){var i,ce="",cd=bx.escape;ce+='<div class="fotorama__nav__frame fotorama__nav__frame--thumb" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__thumb">\r\n </div>\r\n</div>';return ce}})(window,document,location,typeof jQuery!=="undefined"&&jQuery); \ No newline at end of file +fotoramaVersion="4.6.4";(function(bo,k,a3,bV,aP){var ag="fotorama",bH="fotorama__fullscreen",ae=ag+"__wrap",ah=ae+"--css2",aX=ae+"--css3",bt=ae+"--video",ar=ae+"--fade",aw=ae+"--slide",P=ae+"--no-controls",aM=ae+"--no-shadows",U=ae+"--pan-y",a0=ae+"--rtl",az=ae+"--only-active",bN=ae+"--no-captions",f=ae+"--toggle-arrows",a7=ag+"__stage",x=a7+"__frame",l=x+"--video",B=a7+"__shaft",aB=ag+"__grab",bC=ag+"__pointer",aK=ag+"__arr",F=aK+"--disabled",bc=aK+"--prev",r=aK+"--next",bO=ag+"__nav",bq=bO+"-wrap",aH=bO+"__shaft",b=bq+"--vertical",ax=bq+"--list",bZ=bq+"--horizontal",bW=bO+"--dots",ai=bO+"--thumbs",aG=bO+"__frame",br=ag+"__fade",al=br+"-front",n=br+"-rear",aW=ag+"__shadow",bz=aW+"s",S=bz+"--left",aL=bz+"--right",a2=bz+"--top",aR=bz+"--bottom",a4=ag+"__active",a9=ag+"__select",bs=ag+"--hidden",M=ag+"--fullscreen",aJ=ag+"__fullscreen-icon",bP=ag+"__error",bM=ag+"__loading",c=ag+"__loaded",b3=c+"--full",bg=c+"--img",bR=ag+"__grabbing",J=ag+"__img",Y=J+"--full",bS=ag+"__thumb",b0=bS+"__arr--left",H=bS+"__arr--right",cb=bS+"-border",bd=ag+"__html",af=ag+"-video-container",bJ=ag+"__video",T=bJ+"-play",w=bJ+"-close",au=ag+"_horizontal_ratio",aY=ag+"_vertical_ratio",ca=ag+"__spinner",Z=ca+"--show";var E=bV&&bV.fn.jquery.split(".");if(!E||E[0]<1||(E[0]==1&&E[1]<8)){throw"Fotorama requires jQuery 1.8 or later and will not run without it."}var bx={};var ap=(function(co,ct,cj){var cf="2.8.3",cm={},cD=ct.documentElement,cE="modernizr",cB=ct.createElement(cE),cp=cB.style,cg,cw={}.toString,cy=" -webkit- -moz- -o- -ms- ".split(" "),cd="Webkit Moz O ms",cG=cd.split(" "),cq=cd.toLowerCase().split(" "),ck={},ce={},cu={},cA=[],cv=cA.slice,cc,cz=function(cQ,cS,cK,cR){var cJ,cP,cM,cN,cI=ct.createElement("div"),cO=ct.body,cL=cO||ct.createElement("body");if(parseInt(cK,10)){while(cK--){cM=ct.createElement("div");cM.id=cR?cR[cK]:cE+(cK+1);cI.appendChild(cM)}}cJ=["­",'<style id="s',cE,'">',cQ,"</style>"].join("");cI.id=cE;(cO?cI:cL).innerHTML+=cJ;cL.appendChild(cI);if(!cO){cL.style.background="";cL.style.overflow="hidden";cN=cD.style.overflow;cD.style.overflow="hidden";cD.appendChild(cL)}cP=cS(cI,cQ);if(!cO){cL.parentNode.removeChild(cL);cD.style.overflow=cN}else{cI.parentNode.removeChild(cI)}return !!cP},cs=({}).hasOwnProperty,cC;if(!cl(cs,"undefined")&&!cl(cs.call,"undefined")){cC=function(cI,cJ){return cs.call(cI,cJ)}}else{cC=function(cI,cJ){return((cJ in cI)&&cl(cI.constructor.prototype[cJ],"undefined"))}}if(!Function.prototype.bind){Function.prototype.bind=function cH(cK){var cL=this;if(typeof cL!="function"){throw new TypeError()}var cI=cv.call(arguments,1),cJ=function(){if(this instanceof cJ){var cO=function(){};cO.prototype=cL.prototype;var cN=new cO();var cM=cL.apply(cN,cI.concat(cv.call(arguments)));if(Object(cM)===cM){return cM}return cN}else{return cL.apply(cK,cI.concat(cv.call(arguments)))}};return cJ}}function cr(cI){cp.cssText=cI}function ci(cJ,cI){return cr(cy.join(cJ+";")+(cI||""))}function cl(cJ,cI){return typeof cJ===cI}function cn(cJ,cI){return !!~(""+cJ).indexOf(cI)}function cF(cK,cI){for(var cJ in cK){var cL=cK[cJ];if(!cn(cL,"-")&&cp[cL]!==cj){return cI=="pfx"?cL:true}}return false}function cx(cJ,cM,cL){for(var cI in cJ){var cK=cM[cJ[cI]];if(cK!==cj){if(cL===false){return cJ[cI]}if(cl(cK,"function")){return cK.bind(cL||cM)}return cK}}return false}function i(cM,cI,cL){var cJ=cM.charAt(0).toUpperCase()+cM.slice(1),cK=(cM+" "+cG.join(cJ+" ")+cJ).split(" ");if(cl(cI,"string")||cl(cI,"undefined")){return cF(cK,cI)}else{cK=(cM+" "+(cq).join(cJ+" ")+cJ).split(" ");return cx(cK,cI,cL)}}ck.touch=function(){var cI;if(("ontouchstart" in co)||co.DocumentTouch&&ct instanceof DocumentTouch){cI=true}else{cz(["@media (",cy.join("touch-enabled),("),cE,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(cJ){cI=cJ.offsetTop===9})}return cI};ck.csstransforms3d=function(){var cI=!!i("perspective");if(cI&&"webkitPerspective" in cD.style){cz("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(cJ,cK){cI=cJ.offsetLeft===9&&cJ.offsetHeight===3})}return cI};ck.csstransitions=function(){return i("transition")};for(var ch in ck){if(cC(ck,ch)){cc=ch.toLowerCase();cm[cc]=ck[ch]();cA.push((cm[cc]?"":"no-")+cc)}}cm.addTest=function(cJ,cK){if(typeof cJ=="object"){for(var cI in cJ){if(cC(cJ,cI)){cm.addTest(cI,cJ[cI])}}}else{cJ=cJ.toLowerCase();if(cm[cJ]!==cj){return cm}cK=typeof cK=="function"?cK():cK;if(typeof enableClasses!=="undefined"&&enableClasses){cD.className+=" "+(cK?"":"no-")+cJ}cm[cJ]=cK}return cm};cr("");cB=cg=null;cm._version=cf;cm._prefixes=cy;cm._domPrefixes=cq;cm._cssomPrefixes=cG;cm.testProp=function(cI){return cF([cI])};cm.testAllProps=i;cm.testStyles=cz;cm.prefixed=function(cK,cJ,cI){if(!cJ){return i(cK,"pfx")}else{return i(cK,cJ,cI)}};return cm})(bo,k);var bB={ok:false,is:function(){return false},request:function(){},cancel:function(){},event:"",prefix:""},h="webkit moz o ms khtml".split(" ");if(typeof k.cancelFullScreen!="undefined"){bB.ok=true}else{for(var bv=0,N=h.length;bv<N;bv++){bB.prefix=h[bv];if(typeof k[bB.prefix+"CancelFullScreen"]!="undefined"){bB.ok=true;break}}}if(bB.ok){bB.event=bB.prefix+"fullscreenchange";bB.is=function(){switch(this.prefix){case"":return k.fullScreen;case"webkit":return k.webkitIsFullScreen;default:return k[this.prefix+"FullScreen"]}};bB.request=function(i){return(this.prefix==="")?i.requestFullScreen():i[this.prefix+"RequestFullScreen"]()};bB.cancel=function(i){return(this.prefix==="")?k.cancelFullScreen():k[this.prefix+"CancelFullScreen"]()}}function a6(i){var cc="bez_"+bV.makeArray(arguments).join("_").replace(".","p");if(typeof bV.easing[cc]!=="function"){var cd=function(ck,ci){var cf=[null,null],cl=[null,null],cj=[null,null],ch=function(cm,cn){cj[cn]=3*ck[cn];cl[cn]=3*(ci[cn]-ck[cn])-cj[cn];cf[cn]=1-cj[cn]-cl[cn];return cm*(cj[cn]+cm*(cl[cn]+cm*cf[cn]))},cg=function(cm){return cj[0]+cm*(2*cl[0]+3*cf[0]*cm)},ce=function(co){var cm=co,cn=0,cp;while(++cn<14){cp=ch(cm,0)-co;if(Math.abs(cp)<0.001){break}cm-=cp/cg(cm)}return cm};return function(cm){return ch(ce(cm),1)}};bV.easing[cc]=function(cf,cg,ce,ci,ch){return ci*cd([i[0],i[1]],[i[2],i[3]])(cg/ch)+ce}}return cc}var bf=bV(bo),bw=bV(k),R,I,bT=a3.hash.replace("#","")==="quirks",ac=ap.csstransforms3d,aA=ac&&!bT,aN=ac||k.compatMode==="CSS1Compat",s=bB.ok,am=navigator.userAgent.match(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i),bh=!aA||am,aZ=navigator.msPointerEnabled,y="onwheel" in k.createElement("div")?"wheel":k.onmousewheel!==aP?"mousewheel":"DOMMouseScroll",b8=250,ba=300,bY=1400,bQ=5000,b9=2,L=64,bk=500,bE=333,bu="$stageFrame",b7="$navDotFrame",bl="$navThumbFrame",bF="auto",u=a6([0.1,0,0.25,1]),bK=1200,b4=1,Q={width:null,minwidth:null,maxwidth:"100%",height:null,minheight:null,maxheight:null,ratio:null,margin:b9,nav:"dots",navposition:"bottom",navwidth:null,thumbwidth:L,thumbheight:L,thumbmargin:b9,thumbborderwidth:b9,allowfullscreen:false,transition:"slide",clicktransition:null,transitionduration:ba,captions:true,startindex:0,loop:false,autoplay:false,stopautoplayontouch:true,keyboard:false,arrows:true,click:true,swipe:false,trackpad:false,shuffle:false,direction:"ltr",shadows:true,showcaption:true,navdir:"horizontal",navarrows:true,navtype:"thumbs"},p={left:true,right:true,down:true,up:true,space:false,home:false,end:false};function g(){}function bb(cd,cc,i){return Math.max(isNaN(cc)?-Infinity:cc,Math.min(isNaN(i)?Infinity:i,cd))}function bi(cc,i){return cc.match(/ma/)&&cc.match(/-?\d+(?!d)/g)[cc.match(/3d/)?(i==="vertical"?13:12):(i==="vertical"?5:4)]}function aa(cc,i){if(aA){return +bi(cc.css("transform"),i)}else{return +cc.css(i==="vertical"?"top":"left").replace("px","")}}function b2(cd,cc){var i={};if(aA){switch(cc){case"vertical":i.transform="translate3d(0, "+(cd)+"px,0)";break;case"list":break;default:i.transform="translate3d("+(cd)+"px,0,0)";break}}else{cc==="vertical"?i.top=cd:i.left=cd}return i}function b6(i){return{"transition-duration":i+"ms"}}function aV(cc,i){return isNaN(cc)?i:cc}function m(cc,i){return aV(+String(cc).replace(i||"px",""))}function K(i){return/%$/.test(i)?m(i,"%"):aP}function d(cc,i){return aV(K(cc)/100*i,m(cc))}function t(i){return(!isNaN(m(i))||!isNaN(m(i,"%")))&&i}function a8(cc,cd,ce,i){return(cc-(i||0))*(cd+(ce||0))}function by(ce,cc,cd,i){return -Math.round(ce/(cc+(cd||0))-(i||0))}function aO(cd){var cc=cd.data();if(cc.tEnd){return}var ce=cd[0],i={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",msTransition:"MSTransitionEnd",transition:"transitionend"};D(ce,i[ap.prefixed("transition")],function(cf){cc.tProp&&cf.propertyName.match(cc.tProp)&&cc.onEndFn()});cc.tEnd=true}function X(cd,cf,ce,cg){var cc,i=cd.data();if(i){i.onEndFn=function(){if(cc){return}cc=true;clearTimeout(i.tT);ce()};i.tProp=cf;clearTimeout(i.tT);i.tT=setTimeout(function(){i.onEndFn()},cg*1.5);aO(cd)}}function a1(cd,cf){var cc=cd.navdir||"horizontal";if(cd.length){var i=cd.data();if(aA){cd.css(b6(0));i.onEndFn=g;clearTimeout(i.tT)}else{cd.stop()}var ce=bj(cf,function(){return aa(cd,cc)});cd.css(b2(ce,cc));return ce}}function bj(){var cc;for(var cd=0,i=arguments.length;cd<i;cd++){cc=cd?arguments[cd]():arguments[cd];if(typeof cc==="number"){break}}return cc}function aF(cc,i){return Math.round(cc+((i-cc)/1.5))}function aU(){aU.p=aU.p||(a3.protocol==="https:"?"https://":"http://");return aU.p}function ak(cc){var i=k.createElement("a");i.href=cc;return i}function at(i,cd){if(typeof i!=="string"){return i}i=ak(i);var cf,ce;if(i.host.match(/youtube\.com/)&&i.search){cf=i.search.split("v=")[1];if(cf){var cc=cf.indexOf("&");if(cc!==-1){cf=cf.substring(0,cc)}ce="youtube"}}else{if(i.host.match(/youtube\.com|youtu\.be/)){cf=i.pathname.replace(/^\/(embed\/|v\/)?/,"").replace(/\/.*/,"");ce="youtube"}else{if(i.host.match(/vimeo\.com/)){ce="vimeo";cf=i.pathname.replace(/^\/(video\/)?/,"").replace(/\/.*/,"")}}}if((!cf||!ce)&&cd){cf=i.href;ce="custom"}return cf?{id:cf,type:ce,s:i.search.replace(/^\?/,""),p:aU()}:false}function aQ(cg,ce,cf){var cc,i,cd=cg.video;if(cd.type==="youtube"){i=aU()+"img.youtube.com/vi/"+cd.id+"/default.jpg";cc=i.replace(/\/default.jpg$/,"/hqdefault.jpg");cg.thumbsReady=true}else{if(cd.type==="vimeo"){bV.ajax({url:aU()+"vimeo.com/api/v2/video/"+cd.id+".json",dataType:"jsonp",success:function(ch){cg.thumbsReady=true;v(ce,{img:ch[0].thumbnail_large,thumb:ch[0].thumbnail_small},cg.i,cf)}})}else{cg.thumbsReady=true}}return{img:cc,thumb:i}}function v(ch,cd,cf,ci){for(var cg=0,ce=ch.length;cg<ce;cg++){var cj=ch[cg];if(cj.i===cf&&cj.thumbsReady){var cc={videoReady:true};cc[bu]=cc[bl]=cc[b7]=false;ci.splice(cg,1,bV.extend({},cj,cc,cd));break}}}function bI(cc){var ce=[];function cd(cf,ch,cn){var ci=cf.children("img").eq(0),cm=cf.attr("href"),ck=cf.attr("src"),cl=ci.attr("src"),cj=ch.video,cg=cn?at(cm,cj===true):false;if(cg){cm=false}else{cg=cj}i(cf,ci,bV.extend(ch,{video:cg,img:ch.img||cm||ck||cl,thumb:ch.thumb||cl||ck||cm}))}function i(cg,cj,ck){var ci=ck.thumb&&ck.img!==ck.thumb,ch=m(ck.width||cg.attr("width")),cf=m(ck.height||cg.attr("height"));bV.extend(ck,{width:ch,height:cf,thumbratio:bm(ck.thumbratio||(m(ck.thumbwidth||(cj&&cj.attr("width"))||ci||ch)/m(ck.thumbheight||(cj&&cj.attr("height"))||ci||cf)))})}cc.children().each(function(){var cf=bV(this),cg=bA(bV.extend(cf.data(),{id:cf.attr("id")}));if(cf.is("a, img")){cd(cf,cg,true)}else{if(!cf.is(":empty")){i(cf,null,bV.extend(cg,{html:this,_html:cf.html()}))}else{return}}ce.push(cg)});return ce}function W(i){return i.offsetWidth===0&&i.offsetHeight===0}function be(i){return !bV.contains(k.documentElement,i)}function bX(cf,cd,ce,cc){if(!bX.i){bX.i=1;bX.ii=[true]}cc=cc||bX.i;if(typeof bX.ii[cc]==="undefined"){bX.ii[cc]=true}if(cf()){cd()}else{bX.ii[cc]&&setTimeout(function(){bX.ii[cc]&&bX(cf,cd,ce,cc)},ce||100)}return bX.i++}bX.stop=function(cc){bX.ii[cc]=false};function V(ce,cd){var cc=ce.data(),cg=cc.measures;if(cg&&(!cc.l||cc.l.W!==cg.width||cc.l.H!==cg.height||cc.l.r!==cg.ratio||cc.l.w!==cd.w||cc.l.h!==cd.h)){var i=bb(cd.h,0,cg.height),cf=i*cg.ratio;aS.setRatio(ce,cf,i);cc.l={W:cg.width,H:cg.height,r:cg.ratio,w:cd.w,h:cd.h}}return true}function an(i,cd){var cc=i[0];if(cc.styleSheet){cc.styleSheet.cssText=cd}else{i.html(cd)}}function bp(ce,cd,i,cc){return cd===i?false:cc==="vertical"?(ce<=cd?"top":ce>=i?"bottom":"top bottom"):(ce<=cd?"left":ce>=i?"right":"left right")}function z(cc,cd,i){i=i||{};cc.each(function(){var cg=bV(this),cf=cg.data(),ce;if(cf.clickOn){return}cf.clickOn=true;bV.extend(aI(cg,{onStart:function(ch){ce=ch;(i.onStart||g).call(this,ch)},onMove:i.onMove||g,onTouchEnd:i.onTouchEnd||g,onEnd:function(ch){if(ch.moved){return}cd.call(this,ce)}}),{noMove:true})})}function ab(i,cc){return'<div class="'+i+'">'+(cc||"")+"</div>"}function aT(i){return"."+i}function q(i){var cc='<iframe src="'+i.p+i.type+".com/embed/"+i.id+'" frameborder="0" allowfullscreen></iframe>';return cc}function aC(cf){var cc=cf.length;while(cc){var ce=Math.floor(Math.random()*cc--);var cd=cf[cc];cf[cc]=cf[ce];cf[ce]=cd}return cf}function bG(i){return Object.prototype.toString.call(i)=="[object Array]"&&bV.map(i,function(cc){return bV.extend({},cc)})}function bU(i,cd,cc){i.scrollLeft(cd||0).scrollTop(cc||0)}function bA(i){if(i){var cc={};bV.each(i,function(cd,ce){cc[cd.toLowerCase()]=ce});return cc}}function bm(i){if(!i){return}var cc=+i;if(!isNaN(cc)){return cc}else{cc=i.split("/");return +cc[0]/+cc[1]||aP}}function D(cd,ce,cc,i){if(!ce){return}cd.addEventListener?cd.addEventListener(ce,cc,!!i):cd.attachEvent("on"+ce,cc)}function a5(i,cc){if(i>cc.max){i=cc.max}else{if(i<cc.min){i=cc.min}}return i}function aD(i,ck,ch,cf,ce,cd,cc){var cg,cj,ci;if(cc==="horizontal"){cj=i.thumbwidth;ci=cd.width()}else{cj=i.thumbheight;ci=cd.height()}if((cj+i.margin)*(ch+1)>=(ci-cf)){if(cc==="horizontal"){cg=-ce.position().left}else{cg=-ce.position().top}}else{if((cj+i.margin)*(ch)<=Math.abs(cf)){if(cc==="horizontal"){cg=-ce.position().left+ci-(cj+i.margin)}else{cg=-ce.position().top+ci-(cj+i.margin)}}else{cg=cf}}cg=a5(cg,ck);return cg||0}function aj(i){return !!i.getAttribute("disabled")}function ad(cc,i){if(i){return{disabled:cc}}else{return{tabindex:cc*-1+"",disabled:cc}}}function a(cc,i){D(cc,"keyup",function(cd){aj(cc)||cd.keyCode==13&&i.call(cc,cd)})}function bL(cc,i){D(cc,"focus",cc.onfocusin=function(cd){i.call(cc,cd)},true)}function O(cc,i){cc.preventDefault?cc.preventDefault():(cc.returnValue=false);i&&cc.stopPropagation&&cc.stopPropagation()}function aE(cd,cc){var i=/iP(ad|hone|od)/i.test(bo.navigator.userAgent);if(i&&cc==="touchend"){cd.on("touchend",function(ce){bw.trigger("mouseup",ce)})}cd.on(cc,function(ce){O(ce,true);return false})}function ay(i){return i?">":"<"}var aS=(function(){function cd(ch,ce,cg){var cf=ce/cg;if(cf<=1){ch.parent().removeClass(au);ch.parent().addClass(aY)}else{ch.parent().removeClass(aY);ch.parent().addClass(au)}}function i(cf,cg,ch){var ce=ch;if(!cf.attr(ce)&&cf.attr(ce)!==aP){cf.attr(ce,cg)}if(cf.find("["+ce+"]").length){cf.find("["+ce+"]").each(function(){bV(this).attr(ce,cg)})}}function cc(cf,ce,ci){var cg=false,ch;cf.showCaption===ci||cf.showCaption===true?ch=true:ch=false;if(!ce){return false}if(cf.caption&&ch){cg=true}return cg}return{setRatio:cd,setThumbAttr:i,isExpectedCaption:cc}}(aS||{},jQuery));function A(ce,cd){var cc=ce.data(),i=Math.round(cd.pos),cf=function(){if(cc&&cc.sliding){cc.sliding=false}(cd.onEnd||g)()};if(typeof cd.overPos!=="undefined"&&cd.overPos!==cd.pos){i=cd.overPos}var cg=bV.extend(b2(i,cd.direction),cd.width&&{width:cd.width},cd.height&&{height:cd.height});if(cc&&cc.sliding){cc.sliding=true}if(aA){ce.css(bV.extend(b6(cd.time),cg));if(cd.time>10){X(ce,"transform",cf,cd.time)}else{cf()}}else{ce.stop().animate(cg,cd.time,u,cf)}}function aq(ck,cj,cc,cm,ce,i){var ch=typeof i!=="undefined";if(!ch){ce.push(arguments);Array.prototype.push.call(arguments,ce.length);if(ce.length>1){return}}ck=ck||bV(ck);cj=cj||bV(cj);var ci=ck[0],cg=cj[0],cf=cm.method==="crossfade",cl=function(){if(!cl.done){cl.done=true;var cn=(ch||ce.shift())&&ce.shift();cn&&aq.apply(this,cn);(cm.onEnd||g)(!!cn)}},cd=cm.time/(i||1);cc.removeClass(n+" "+al);ck.stop().addClass(n);cj.stop().addClass(al);cf&&cg&&ck.fadeTo(0,0);ck.fadeTo(cf?cd:0,1,cf&&cl);cj.fadeTo(cd,0,cl);(ci&&cf)||cg||cl()}var G,b5,e,j,bD;function bn(i){var cc=(i.touches||[])[0]||i;i._x=cc.pageX||cc.originalEvent.pageX;i._y=cc.clientY||cc.originalEvent.clientY;i._now=bV.now()}function aI(cr,cg){var cc=cr[0],cj={},i,cl,cf,cn,cs,cd,ce,co,ch;function cq(ct){cf=bV(ct.target);cj.checked=cd=ce=ch=false;if(i||cj.flow||(ct.touches&&ct.touches.length>1)||ct.which>1||(G&&G.type!==ct.type&&e)||(cd=cg.select&&cf.is(cg.select,cc))){return cd}cs=ct.type==="touchstart";ce=cf.is("a, a *",cc);cn=cj.control;co=(cj.noMove||cj.noSwipe||cn)?16:!cj.snap?4:0;bn(ct);cl=G=ct;b5=ct.type.replace(/down|start/,"move").replace(/Down/,"Move");(cg.onStart||g).call(cc,ct,{control:cn,$target:cf});i=cj.flow=true;if(!cs||cj.go){O(ct)}}function ck(cx){if((cx.touches&&cx.touches.length>1)||(aZ&&!cx.isPrimary)||b5!==cx.type||!i){i&&ci();(cg.onTouchEnd||g)();return}bn(cx);var cy=Math.abs(cx._x-cl._x),cu=Math.abs(cx._y-cl._y),cw=cy-cu,cv=(cj.go||cj.x||cw>=0)&&!cj.noSwipe,ct=cw<0;if(cs&&!cj.checked){if(i=cv){O(cx)}}else{O(cx);(cg.onMove||g).call(cc,cx,{touch:cs})}if(!ch&&Math.sqrt(Math.pow(cy,2)+Math.pow(cu,2))>co){ch=true}cj.checked=cj.checked||cv||ct}function ci(cu){(cg.onTouchEnd||g)();var ct=i;cj.control=i=false;if(ct){cj.flow=false}if(!ct||(ce&&!cj.checked)){return}cu&&O(cu);e=true;clearTimeout(j);j=setTimeout(function(){e=false},1000);(cg.onEnd||g).call(cc,{moved:ch,$target:cf,control:cn,touch:cs,startEvent:cl,aborted:!cu||cu.type==="MSPointerCancel"})}function cm(){if(cj.flow){return}cj.flow=true}function cp(){if(!cj.flow){return}cj.flow=false}if(aZ){D(cc,"MSPointerDown",cq);D(k,"MSPointerMove",ck);D(k,"MSPointerCancel",ci);D(k,"MSPointerUp",ci)}else{D(cc,"touchstart",cq);D(cc,"touchmove",ck);D(cc,"touchend",ci);D(k,"touchstart",cm);D(k,"touchend",cp);D(k,"touchcancel",cp);bf.on("scroll",cp);cr.on("mousedown pointerdown",cq);bw.on("mousemove pointermove",ck).on("mouseup pointerup",ci)}if(ap.touch){bD="a"}else{bD="div"}cr.on("click",bD,function(ct){cj.checked&&O(ct)});return cj}function ao(cz,cd){var cc=cz[0],ce=cz.data(),cm={},cw,cf,cx,cj,ch,cy,co,cg,cr,ct,cp,cq,i,cv,ci,cn;function cs(cA,cB){cn=true;cw=cf=(cq==="vertical")?cA._y:cA._x;co=cA._now;cy=[[co,cw]];cx=cj=cm.noMove||cB?0:a1(cz,(cd.getPos||g)());(cd.onStart||g).call(cc,cA)}function cu(cB,cA){cr=cm.min;ct=cm.max;cp=cm.snap,cq=cm.direction||"horizontal",cz.navdir=cq;i=cB.altKey;cn=ci=false;cv=cA.control;if(!cv&&!ce.sliding){cs(cB)}}function cl(cB,cA){if(!cm.noSwipe){if(!cn){cs(cB)}cf=(cq==="vertical")?cB._y:cB._x;cy.push([cB._now,cf]);cj=cx-(cw-cf);ch=bp(cj,cr,ct,cq);if(cj<=cr){cj=aF(cj,cr)}else{if(cj>=ct){cj=aF(cj,ct)}}if(!cm.noMove){cz.css(b2(cj,cq));if(!ci){ci=true;cA.touch||aZ||cz.addClass(bR)}(cd.onMove||g).call(cc,cB,{pos:cj,edge:ch})}}}function ck(cJ){if(cm.noSwipe&&cJ.moved){return}if(!cn){cs(cJ.startEvent,true)}cJ.touch||aZ||cz.removeClass(bR);cg=bV.now();var cG=cg-b8,cK,cP,cQ,cS=null,cA,cE,cN,cD,cF,cI=ba,cO,cH=cd.friction;for(var cC=cy.length-1;cC>=0;cC--){cK=cy[cC][0];cP=Math.abs(cK-cG);if(cS===null||cP<cQ){cS=cK;cA=cy[cC][1]}else{if(cS===cG||cP>cQ){break}}cQ=cP}cD=bb(cj,cr,ct);var cT=cA-cf,cR=cT>=0,cL=cg-cS,cB=cL>b8,cM=!cB&&cj!==cx&&cD===cj;if(cp){cD=bb(Math[cM?(cR?"floor":"ceil"):"round"](cj/cp)*cp,cr,ct);cr=ct=cD}if(cM&&(cp||cD===cj)){cO=-(cT/cL);cI*=bb(Math.abs(cO),cd.timeLow,cd.timeHigh);cE=Math.round(cj+cO*cI/cH);if(!cp){cD=cE}if(!cR&&cE>ct||cR&&cE<cr){cN=cR?cr:ct;cF=cE-cN;if(!cp){cD=cN}cF=bb(cD+cF*0.03,cN-50,cN+50);cI=Math.abs((cj-cF)/(cO/cH))}}cI*=i?10:1;(cd.onEnd||g).call(cc,bV.extend(cJ,{moved:cJ.moved||cB&&cp,pos:cj,newPos:cD,overPos:cF,time:cI,dir:cq}))}cm=bV.extend(aI(cd.$wrap,bV.extend({},cd,{onStart:cu,onMove:cl,onEnd:ck})),cm);return cm}function o(ce,cd){var cg=ce[0],ch,cf,i,cc={prevent:{}};D(cg,y,function(co){var cl=co.wheelDeltaY||-1*co.deltaY||0,cn=co.wheelDeltaX||-1*co.deltaX||0,ck=Math.abs(cn)&&!Math.abs(cl),cm=ay(cn<0),cp=cf===cm,ci=bV.now(),cj=ci-i<b8;cf=cm;i=ci;if(!ck||!cc.ok||cc.prevent[cm]&&!ch){return}else{O(co,true);if(ch&&cp&&cj){return}}if(cd.shift){ch=true;clearTimeout(cc.t);cc.t=setTimeout(function(){ch=false},bY)}(cd.onEnd||g)(co,cd.shift?cm:cn)});return cc}jQuery.Fotorama=function(d6,c3){R=bV("html");I=bV("body");var cg=this,cC=bV.now(),cQ=ag+cC,eu=d6[0],dP,cY=1,cR=d6.data(),c1,de=bV("<style></style>"),c9=bV(ab(bs)),dk=d6.find(aT(ae)),cf=dk.find(aT(a7)),dY=cf[0],cl=d6.find(aT(B)),c8=bV(),dW=d6.find(aT(bc)),da=d6.find(aT(r)),cU=d6.find(aT(aK)),dU=d6.find(aT(bq)),dO=dU.find(aT(bO)),cF=dO.find(aT(aH)),dA,cB=bV(),cW=bV(),dS=cl.data(),cX=cF.data(),c7=d6.find(aT(cb)),eg=d6.find(aT(b0)),dX=d6.find(aT(H)),dM=d6.find(aT(aJ)),dD=dM[0],cH=bV(ab(T)),dt=d6.find(aT(w)),d1=dt[0],eb=d6.find(aT(ca)),dg,eo=false,dF,ea,c2,ed,dw,d4,cN,cK,dx,dj,cq,c0,d8,c4,d2,cv,ch,ej,ds,cu,ec,dH,dE,d0={},en={},dG,d5={},cG={},dy={},ef={},cs,cT,ee,cj,el,cd={},er={},dZ,c6,dz,dr,d3=0,cI=[];dk[bu]=bV('<div class="'+x+'"></div>');dk[bl]=bV(bV.Fotorama.jst.thumb());dk[b7]=bV(bV.Fotorama.jst.dots());cd[bu]=[];cd[bl]=[];cd[b7]=[];er[bu]={};dk.addClass(aA?aX:ah);cR.fotorama=this;function ep(){bV.each(dP,function(ey,eA){if(!eA.i){eA.i=cY++;var ez=at(eA.video,true);if(ez){var ex={};eA.video=ez;if(!eA.img&&!eA.thumb){ex=aQ(eA,dP,cg)}else{eA.thumbsReady=true}v(dP,{img:ex.img,thumb:ex.thumb},eA.i,cg)}}})}function df(ex){return dE[ex]}function i(){if(cf!==aP){if(c3.navdir=="vertical"){var ex=c3.thumbwidth+c3.thumbmargin;cf.css("left",ex);da.css("right",ex);dM.css("right",ex);dk.css("width",dk.css("width")+ex);cl.css("max-width",dk.width()-ex)}else{cf.css("left","");da.css("right","");dM.css("right","");dk.css("width",dk.css("width")+ex);cl.css("max-width","")}}}function ek(eB){var eC="keydown."+ag,eD=ag+cC,ex="keydown."+eD,eA="keyup."+eD,ey="resize."+eD+" orientationchange."+eD,ez;if(eB){bw.on(ex,function(eG){var eF,eE;if(dg&&eG.keyCode===27){eF=true;cO(dg,true,true)}else{if(cg.fullScreen||(c3.keyboard&&!cg.index)){if(eG.keyCode===27){eF=true;cg.cancelFullScreen()}else{if((eG.shiftKey&&eG.keyCode===32&&df("space"))||(!eG.altKey&&!eG.metaKey&&eG.keyCode===37&&df("left"))||(eG.keyCode===38&&df("up")&&bV(":focus").attr("data-gallery-role"))){cg.longPress.progress();eE="<"}else{if((eG.keyCode===32&&df("space"))||(!eG.altKey&&!eG.metaKey&&eG.keyCode===39&&df("right"))||(eG.keyCode===40&&df("down")&&bV(":focus").attr("data-gallery-role"))){cg.longPress.progress();eE=">"}else{if(eG.keyCode===36&&df("home")){cg.longPress.progress();eE="<<"}else{if(eG.keyCode===35&&df("end")){cg.longPress.progress();eE=">>"}}}}}}}(eF||eE)&&O(eG);ez={index:eE,slow:eG.altKey,user:true};eE&&(cg.longPress.inProgress?cg.showWhileLongPress(ez):cg.show(ez))});if(eB){bw.on(eA,function(eE){if(cg.longPress.inProgress){cg.showEndLongPress({user:true})}cg.longPress.reset()})}if(!cg.index){bw.off(eC).on(eC,"textarea, input, select",function(eE){!I.hasClass(bH)&&eE.stopPropagation()})}bf.on(ey,cg.resize)}else{bw.off(ex);bf.off(ey)}}function dd(ex){if(ex===dd.f){return}if(ex){d6.addClass(ag+" "+cQ).before(c9).before(de);C(cg)}else{c9.detach();de.detach();d6.html(cR.urtext).removeClass(cQ);av(cg)}ek(ex);dd.f=ex}function dn(){dP=cg.data=dP||bG(c3.data)||bI(d6);c1=cg.size=dP.length;eq.ok&&c3.shuffle&&aC(dP);ep();eo=cn(eo);c1&&dd(true)}function em(){var ex=c1<2||dg;d5.noMove=ex||cv;d5.noSwipe=ex||!c3.swipe;!cu&&cl.toggleClass(aB,!c3.click&&!d5.noMove&&!d5.noSwipe);aZ&&dk.toggleClass(U,!d5.noSwipe)}function dq(ex){if(ex===true){ex=""}c3.autoplay=Math.max(+ex||bQ,ds*1.5)}function db(ex){if(ex.navarrows&&ex.nav==="thumbs"){eg.show();dX.show()}else{eg.hide();dX.hide()}}function ck(ex,ey){return Math.floor(dk.width()/(ey.thumbwidth+ey.thumbmargin))}function dQ(){if(!c3.nav||c3.nav==="dots"){c3.navdir="horizontal"}cg.options=c3=bA(c3);b4=ck(dk,c3);cv=(c3.transition==="crossfade"||c3.transition==="dissolve");dj=c3.loop&&(c1>2||(cv&&(!cu||cu!=="slide")));ds=+c3.transitionduration||ba;dH=c3.direction==="rtl";dE=bV.extend({},c3.keyboard&&p,c3.keyboard);db(c3);var ey={add:[],remove:[]};function ex(ez,eA){ey[ez?"add":"remove"].push(eA)}if(c1>1){cq=c3.nav;d8=c3.navposition==="top";ey.remove.push(a9);cU.toggle(!!c3.arrows)}else{cq=false;cU.hide()}dh();cJ();ev();if(c3.autoplay){dq(c3.autoplay)}ch=m(c3.thumbwidth)||L;ej=m(c3.thumbheight)||L;cG.ok=ef.ok=c3.trackpad&&!bh;em();dL(c3,[en]);c0=cq==="thumbs";if(dU.filter(":hidden")&&!!cq){dU.show()}if(c0){dl(c1,"navThumb");dA=cW;dr=bl;an(de,bV.Fotorama.jst.style({w:ch,h:ej,b:c3.thumbborderwidth,m:c3.thumbmargin,s:cC,q:!aN}));dO.addClass(ai).removeClass(bW)}else{if(cq==="dots"){dl(c1,"navDot");dA=cB;dr=b7;dO.addClass(bW).removeClass(ai)}else{dU.hide();cq=false;dO.removeClass(ai+" "+bW)}}if(cq){if(d8){dU.insertBefore(cf)}else{dU.insertAfter(cf)}cz.nav=false;cz(dA,cF,"nav")}c4=c3.allowfullscreen;if(c4){dM.prependTo(cf);d2=s&&c4==="native";aE(dM,"touchend")}else{dM.detach();d2=false}ex(cv,ar);ex(!cv,aw);ex(!c3.captions,bN);ex(dH,a0);ex(c3.arrows,f);ec=c3.shadows&&!bh;ex(!ec,aM);dk.addClass(ey.add.join(" ")).removeClass(ey.remove.join(" "));d0=bV.extend({},c3);i()}function cZ(ex){return ex<0?(c1+(ex%c1))%c1:ex>=c1?ex%c1:ex}function cn(ex){return bb(ex,0,c1-1)}function du(ex){return dj?cZ(ex):cn(ex)}function dB(ex){return ex>0||dj?ex-1:false}function ci(ex){return ex<c1-1||dj?ex+1:false}function d9(){d5.min=dj?-Infinity:-a8(c1-1,en.w,c3.margin,c2);d5.max=dj?Infinity:-a8(0,en.w,c3.margin,c2);d5.snap=en.w+c3.margin}function c5(){var ex=(c3.navdir==="vertical");var ez=ex?cF.height():cF.width();var ey=ex?en.h:en.nw;dy.min=Math.min(0,ey-ez);dy.max=0;dy.direction=c3.navdir;cF.toggleClass(aB,!(dy.noMove=dy.min===dy.max))}function dm(ey,eA,ez){if(typeof ey==="number"){ey=new Array(ey);var ex=true}return bV.each(ey,function(eD,eB){if(ex){eB=eD}if(typeof eB==="number"){var eF=dP[cZ(eB)];if(eF){var eC="$"+eA+"Frame",eE=eF[eC];ez.call(this,eD,eB,eF,eE,eC,eE&&eE.data())}}})}function cc(eA,ex,ez,ey){if(!dG||(dG==="*"&&ey===dx)){eA=t(c3.width)||t(eA)||bk;ex=t(c3.height)||t(ex)||bE;cg.resize({width:eA,ratio:c3.ratio||ez||eA/ex},0,ey!==dx&&"*")}}function cx(ex,ey,eA,ez){dm(ex,ey,function(eM,eE,eD,eC,eR,eB){if(!eC){return}var eN=cg.fullScreen&&!eB.$full&&ey==="stage";if(eB.$img&&!ez&&!eN){return}var eS=new Image(),eG=bV(eS),eO=eG.data();eB[eN?"$full":"$img"]=eG;var eJ=ey==="stage"?(eN?"full":"img"):"thumb",eF=eD[eJ],eP=eN?eD.img:eD[ey==="stage"?"thumb":"img"];if(ey==="navThumb"){eC=eB.$wrap}function eH(eT){var eU=cZ(eE);dc(eT,{index:eU,src:eF,frame:dP[eU]})}function eK(){eG.remove();bV.Fotorama.cache[eF]="error";if((!eD.html||ey!=="stage")&&eP&&eP!==eF){eD[eJ]=eF=eP;eB.$full=null;cx([eE],ey,eA,true)}else{if(eF&&!eD.html&&!eN){eC.trigger("f:error").removeClass(bM).addClass(bP);eH("error")}else{if(ey==="stage"){eC.trigger("f:load").removeClass(bM+" "+bP).addClass(c);eH("load");cc()}}eB.state="error";if(c1>1&&dP[eE]===eD&&!eD.html&&!eD.deleted&&!eD.video&&!eN){eD.deleted=true;cg.splice(eE,1)}}}function eL(){bV.Fotorama.measures[eF]=eO.measures=bV.Fotorama.measures[eF]||{width:eS.width,height:eS.height,ratio:eS.width/eS.height};cc(eO.measures.width,eO.measures.height,eO.measures.ratio,eE);eG.off("load error").addClass(""+(eN?Y:J)).attr("aria-hidden","false").prependTo(eC);if(eC.hasClass(x)&&!eC.hasClass(af)){eC.attr("href",eG.attr("src"))}V(eG,(bV.isFunction(eA)?eA():eA)||en);bV.Fotorama.cache[eF]=eB.state="loaded";setTimeout(function(){eC.trigger("f:load").removeClass(bM+" "+bP).addClass(c+" "+(eN?b3:bg));if(ey==="stage"){eH("load")}else{if(eD.thumbratio===bF||!eD.thumbratio&&c3.thumbratio===bF){eD.thumbratio=eO.measures.ratio;dV()}}},0)}if(!eF){eK();return}function eI(){var eT=10;bX(function(){return !c6||!eT--&&!bh},function(){eL()})}if(!bV.Fotorama.cache[eF]){bV.Fotorama.cache[eF]="*";eG.on("load",eI).on("error",eK)}else{(function eQ(){if(bV.Fotorama.cache[eF]==="error"){eK()}else{if(bV.Fotorama.cache[eF]==="loaded"){setTimeout(eI,0)}else{setTimeout(eQ,100)}}})()}eB.state="";eS.src=eF;if(eB.data.caption){eS.alt=eB.data.caption||""}if(eB.data.full){bV(eS).data("original",eB.data.full)}if(aS.isExpectedCaption(eD,c3.showcaption)){bV(eS).attr("aria-labelledby",eD.labelledby)}})}function cy(){var ex=dF[bu];if(ex&&!ex.data().state){eb.addClass(Z);ex.on("f:load f:error",function(){ex.off("f:load f:error");eb.removeClass(Z)})}}function cL(ex){a(ex,dJ);bL(ex,function(){setTimeout(function(){bU(dO)},0);dT({time:ds,guessIndex:bV(this).data().eq,minMax:dy})})}function dl(ex,ey){dm(ex,ey,function(eB,ez,eG,eD,eA,eC){if(eD){return}eD=eG[eA]=dk[eA].clone();eC=eD.data();eC.data=eG;var eF=eD[0],eE="labelledby"+bV.now();if(ey==="stage"){if(eG.html){bV('<div class="'+bd+'"></div>').append(eG._html?bV(eG.html).removeAttr("id").html(eG._html):eG.html).appendTo(eD)}if(eG.id){eE=eG.id||eE}eG.labelledby=eE;if(aS.isExpectedCaption(eG,c3.showcaption)){bV(bV.Fotorama.jst.frameCaption({caption:eG.caption,labelledby:eE})).appendTo(eD)}eG.video&&eD.addClass(l).append(cH.clone());bL(eF,function(){setTimeout(function(){bU(cf)},0);cm({index:eC.eq,user:true})});c8=c8.add(eD)}else{if(ey==="navDot"){cL(eF);cB=cB.add(eD)}else{if(ey==="navThumb"){cL(eF);eC.$wrap=eD.children(":first");cW=cW.add(eD);if(eG.video){eC.$wrap.append(cH.clone())}}}}})}function cM(ey,ex){return ey&&ey.length&&V(ey,ex)}function di(ex){dm(ex,"stage",function(eB,ez,eE,eD,eA,eC){if(!eD){return}var ey=cZ(ez);eC.eq=ey;er[bu][ey]=eD.css(bV.extend({left:cv?0:a8(ez,en.w,c3.margin,c2)},cv&&b6(0)));if(be(eD[0])){eD.appendTo(cl);cO(eE.$video)}cM(eC.$img,en);cM(eC.$full,en);if(eD.hasClass(x)&&!(eD.attr("aria-hidden")==="false"&&eD.hasClass(a4))){eD.attr("aria-hidden","true")}})}function dp(eB,ex){var ey,ez,eA;if(cq!=="thumbs"||isNaN(eB)){return}ey=-eB;ez=-eB+en.nw;if(c3.navdir==="vertical"){eB=eB-c3.thumbheight;ez=-eB+en.h}cW.each(function(){var eH=bV(this),eD=eH.data(),eC=eD.eq,eG=function(){return{h:ej,w:eD.w}},eF=eG(),eE=c3.navdir==="vertical"?eD.t>ez:eD.l>ez;eF.w=eD.w;if(eD.l+eD.w<ey||eE||cM(eD.$img,eF)){return}ex&&cx([eC],"navThumb",eG)})}function cz(ex,eC,ey){if(!cz[ey]){var eB=ey==="nav"&&c0,eA=0,ez=0;eC.append(ex.filter(function(){var eH,eG=bV(this),eE=eG.data();for(var eF=0,eD=dP.length;eF<eD;eF++){if(eE.data===dP[eF]){eH=true;eE.eq=eF;break}}return eH||eG.remove()&&false}).sort(function(eE,eD){return bV(eE).data().eq-bV(eD).data().eq}).each(function(){var eE=bV(this),eD=eE.data();aS.setThumbAttr(eE,eD.data.caption,"aria-label")}).each(function(){if(!eB){return}var eF=bV(this),eE=eF.data(),eG=Math.round(ej*eE.data.thumbratio)||ch,eD=Math.round(ch/eE.data.thumbratio)||ej;eE.t=ez;eE.h=eD;eE.l=eA;eE.w=eG;eF.css({width:eG});ez+=eD+c3.thumbmargin;eA+=eG+c3.thumbmargin}));cz[ey]=true}}function eh(ex){return ex-d3>en.w/3}function cE(ex){return !dj&&(!(eo+ex)||!(eo-c1+ex))&&!dg}function dh(){var ey=cE(0),ex=cE(1);dW.toggleClass(F,ey).attr(ad(ey,false));da.toggleClass(F,ex).attr(ad(ex,false))}function ev(){var ex=false,ey=false;if(c3.navtype==="thumbs"&&!c3.loop){(eo==0)?ex=true:ex=false;(eo==c3.data.length-1)?ey=true:ey=false}if(c3.navtype==="slides"){var ez=aa(cF,c3.navdir);ez>=dy.max?ex=true:ex=false;ez<=dy.min?ey=true:ey=false}eg.toggleClass(F,ex).attr(ad(ex,true));dX.toggleClass(F,ey).attr(ad(ey,true))}function cJ(){if(cG.ok){cG.prevent={"<":cE(0),">":cE(1)}}}function dI(eD){var eA=eD.data(),eC,eB,ez,ex;if(c0){eC=eA.l;eB=eA.t;ez=eA.w;ex=eA.h}else{eC=eD.position().left;ez=eD.width()}var ey={c:eC+ez/2,min:-eC+c3.thumbmargin*10,max:-eC+en.w-ez-c3.thumbmargin*10};var eE={c:eB+ex/2,min:-eB+c3.thumbmargin*10,max:-eB+en.h-ex-c3.thumbmargin*10};return c3.navdir==="vertical"?eE:ey}function d7(ey){var ex=dF[dr].data();A(c7,{time:ey*1.2,pos:(c3.navdir==="vertical"?ex.t:ex.l),width:ex.w,height:ex.h,direction:c3.navdir})}function dT(eH){var eB=dP[eH.guessIndex][dr],ez=c3.navtype;var eD,ex,eA,eG,eC,ey,eE,eF;if(eB){if(ez==="thumbs"){eD=dy.min!==dy.max;eA=eH.minMax||eD&&dI(dF[dr]);eG=eD&&(eH.keep&&dT.t?dT.l:bb((eH.coo||en.nw/2)-dI(eB).c,eA.min,eA.max));eC=eD&&(eH.keep&&dT.l?dT.l:bb((eH.coo||en.nw/2)-dI(eB).c,eA.min,eA.max));ey=(c3.navdir==="vertical"?eG:eC);eE=eD&&bb(ey,dy.min,dy.max)||0;ex=eH.time*1.1;A(cF,{time:ex,pos:eE,direction:c3.navdir,onEnd:function(){dp(eE,true);ev()}});co(dO,bp(eE,dy.min,dy.max,c3.navdir));dT.l=ey}else{eF=aa(cF,c3.navdir);ex=eH.time*1.11;eE=aD(c3,dy,eH.guessIndex,eF,eB,dU,c3.navdir);A(cF,{time:ex,pos:eE,direction:c3.navdir,onEnd:function(){dp(eE,true);ev()}});co(dO,bp(eE,dy.min,dy.max,c3.navdir))}}}function cS(){dN(dr);cd[dr].push(dF[dr].addClass(a4).attr("data-active",true))}function dN(ey){var ex=cd[ey];while(ex.length){ex.shift().removeClass(a4).attr("data-active",false)}}function ce(ey){var ex=er[ey];bV.each(ea,function(eA,ez){delete ex[cZ(ez)]});bV.each(ex,function(ez,eA){delete ex[ez];eA.detach()})}function dC(ey){c2=ed=eo;var ex=dF[bu];if(ex){dN(bu);cd[bu].push(ex.addClass(a4).attr("data-active",true));if(ex.hasClass(x)){ex.attr("aria-hidden","false")}ey||cg.showStage.onEnd(true);a1(cl,0,true);ce(bu);di(ea);d9();c5();a(cl[0],function(){if(!d6.hasClass(M)){cg.requestFullScreen();dM.focus()}})}}function dL(ey,ex){if(!ey){return}bV.each(ex,function(ez,eA){if(!eA){return}bV.extend(eA,{width:ey.width||eA.width,height:ey.height,minwidth:ey.minwidth,maxwidth:ey.maxwidth,minheight:ey.minheight,maxheight:ey.maxheight,ratio:bm(ey.ratio)})})}function dc(ey,ex){d6.trigger(ag+":"+ey,[cg,ex])}function dR(){clearTimeout(cr.t);c6=1;if(c3.stopautoplayontouch){cg.stopAutoplay()}else{cj=true}}function cr(){if(!c6){return}if(!c3.stopautoplayontouch){cw();es()}cr.t=setTimeout(function(){c6=0},ba+b8)}function cw(){cj=!!(dg||el)}function es(){clearTimeout(es.t);bX.stop(es.w);if(!c3.autoplay||cj){if(cg.autoplay){cg.autoplay=false;dc("stopautoplay")}return}if(!cg.autoplay){cg.autoplay=true;dc("startautoplay")}var ey=eo;var ex=dF[bu].data();es.w=bX(function(){return ex.state||ey!==eo},function(){es.t=setTimeout(function(){if(cj||ey!==eo){return}var ez=cK,eA=dP[ez][bu].data();es.w=bX(function(){return eA.state||ez!==cK},function(){if(cj||ez!==cK){return}cg.show(dj?ay(!dH):cK)})},c3.autoplay)})}cg.startAutoplay=function(ex){if(cg.autoplay){return this}cj=el=false;dq(ex||c3.autoplay);es();return this};cg.stopAutoplay=function(){if(cg.autoplay){cj=el=true;es()}return this};cg.showSlide=function(ez){var eA=aa(cF,c3.navdir),eC,eB=500*1.1,ey=c3.navdir==="horizontal"?c3.thumbwidth:c3.thumbheight,ex=function(){ev()};if(ez==="next"){eC=eA-(ey+c3.margin)*b4}if(ez==="prev"){eC=eA+(ey+c3.margin)*b4}eC=a5(eC,dy);dp(eC,true);A(cF,{time:eB,pos:eC,direction:c3.navdir,onEnd:ex})};cg.showWhileLongPress=function(eA){if(cg.longPress.singlePressInProgress){return}var ez=dK(eA);ew(ez);var eB=cA(eA)/50;var ey=dF;cg.activeFrame=dF=dP[eo];var ex=ey===dF&&!eA.user;cg.showNav(ex,eA,eB);return this};cg.showEndLongPress=function(eA){if(cg.longPress.singlePressInProgress){return}var ez=dK(eA);ew(ez);var eB=cA(eA)/50;var ey=dF;cg.activeFrame=dF=dP[eo];var ex=ey===dF&&!eA.user;cg.showStage(ex,eA,eB);ee=typeof dw!=="undefined"&&dw!==eo;dw=eo;return this};function dK(ey){var ex;if(typeof ey!=="object"){ex=ey;ey={}}else{ex=ey.index}ex=ex===">"?ed+1:ex==="<"?ed-1:ex==="<<"?0:ex===">>"?c1-1:ex;ex=isNaN(ex)?aP:ex;ex=typeof ex==="undefined"?eo||0:ex;return ex}function ew(ex){cg.activeIndex=eo=du(ex);d4=dB(eo);cN=ci(eo);cK=cZ(eo+(dH?-1:1));ea=[eo,d4,cN];ed=dj?ex:eo}function cA(ey){var ex=Math.abs(dw-ed),ez=bj(ey.time,function(){return Math.min(ds*(1+(ex-1)/12),ds*2)});if(ey.slow){ez*=10}return ez}cg.showStage=function(ey,eA,eD){cO(dg,dF.i!==dP[cZ(c2)].i);dl(ea,"stage");di(bh?[ed]:[ed,dB(ed),ci(ed)]);cD("go",true);ey||dc("show",{user:eA.user,time:eD});cj=true;var eC=eA.overPos;var ez=cg.showStage.onEnd=function(eE){if(ez.ok){return}ez.ok=true;eE||dC(true);if(!ey){dc("showend",{user:eA.user})}if(!eE&&cu&&cu!==c3.transition){cg.setOptions({transition:cu});cu=false;return}cy();cx(ea,"stage");cD("go",false);cJ();ei();cw();es();if(cg.fullScreen){dF[bu].find("."+Y).attr("aria-hidden",false);dF[bu].find("."+J).attr("aria-hidden",true)}else{dF[bu].find("."+Y).attr("aria-hidden",true);dF[bu].find("."+J).attr("aria-hidden",false)}};if(!cv){A(cl,{pos:-a8(ed,en.w,c3.margin,c2),overPos:eC,time:eD,onEnd:ez})}else{var ex=dF[bu],eB=dP[dw]&&eo!==dw?dP[dw][bu]:null;aq(ex,eB,c8,{time:eD,method:c3.transition,onEnd:ez},cI)}dh()};cg.showNav=function(ey,ez,eA){ev();if(cq){cS();var ex=cn(eo+bb(ed-dw,-1,1));dT({time:eA,coo:ex!==eo&&ez.coo,guessIndex:typeof ez.coo!=="undefined"?ex:eo,keep:ey});if(c0){d7(eA)}}};cg.show=function(eA){cg.longPress.singlePressInProgress=true;var ez=dK(eA);ew(ez);var eB=cA(eA);var ey=dF;cg.activeFrame=dF=dP[eo];var ex=ey===dF&&!eA.user;cg.showStage(ex,eA,eB);cg.showNav(ex,eA,eB);ee=typeof dw!=="undefined"&&dw!==eo;dw=eo;cg.longPress.singlePressInProgress=false;return this};cg.requestFullScreen=function(){if(c4&&!cg.fullScreen){var ex=bV((cg.activeFrame||{}).$stageFrame||{}).hasClass("fotorama-video-container");if(ex){return}cs=bf.scrollTop();cT=bf.scrollLeft();bU(bf);cD("x",true);dZ=bV.extend({},en);d6.addClass(M).appendTo(I.addClass(bH));R.addClass(bH);cO(dg,true,true);cg.fullScreen=true;if(d2){bB.request(eu)}cg.resize();cx(ea,"stage");cy();dc("fullscreenenter");if(!("ontouchstart" in bo)){dM.focus()}}return this};function cP(){if(cg.fullScreen){cg.fullScreen=false;if(s){bB.cancel(eu)}I.removeClass(bH);R.removeClass(bH);d6.removeClass(M).insertAfter(c9);en=bV.extend({},dZ);cO(dg,true,true);cD("x",false);cg.resize();cx(ea,"stage");bU(bf,cT,cs);dc("fullscreenexit")}}cg.cancelFullScreen=function(){if(d2&&bB.is()){bB.cancel(k)}else{cP()}return this};cg.toggleFullScreen=function(){return cg[(cg.fullScreen?"cancel":"request")+"FullScreen"]()};cg.resize=function(ez){if(!dP){return this}var eC=arguments[1]||0,ey=arguments[2];b4=ck(dk,c3);dL(!cg.fullScreen?bA(ez):{width:bV(bo).width(),maxwidth:null,minwidth:null,height:bV(bo).height(),maxheight:null,minheight:null},[en,ey||cg.fullScreen||c3]);var eB=en.width,ex=en.height,eA=en.ratio,eD=bf.height()-(cq?dO.height():0);if(t(eB)){dk.css({width:""});dk.css({height:""});cf.css({width:""});cf.css({height:""});cl.css({width:""});cl.css({height:""});dO.css({width:""});dO.css({height:""});dk.css({minWidth:en.minwidth||0,maxWidth:en.maxwidth||bK});if(cq==="dots"){dU.hide()}eB=en.W=en.w=dk.width();en.nw=cq&&d(c3.navwidth,eB)||eB;cl.css({width:en.w,marginLeft:(en.W-en.w)/2});ex=d(ex,eD);ex=ex||(eA&&eB/eA);if(ex){eB=Math.round(eB);ex=en.h=Math.round(bb(ex,d(en.minheight,eD),d(en.maxheight,eD)));cf.css({width:eB,height:ex});if(c3.navdir==="vertical"&&!cg.fullscreen){dO.width(c3.thumbwidth+c3.thumbmargin*2)}if(c3.navdir==="horizontal"&&!cg.fullscreen){dO.height(c3.thumbheight+c3.thumbmargin*2)}if(cq==="dots"){dO.width(eB).height("auto");dU.show()}if(c3.navdir==="vertical"&&cg.fullScreen){cf.css("height",bf.height())}if(c3.navdir==="horizontal"&&cg.fullScreen){cf.css("height",bf.height()-dO.height())}if(cq){switch(c3.navdir){case"vertical":dU.removeClass(bZ);dU.removeClass(ax);dU.addClass(b);dO.stop().animate({height:en.h,width:c3.thumbwidth},eC);break;case"list":dU.removeClass(b);dU.removeClass(bZ);dU.addClass(ax);break;default:dU.removeClass(b);dU.removeClass(ax);dU.addClass(bZ);dO.stop().animate({width:en.nw},eC);break}dC();dT({guessIndex:eo,time:eC,keep:true});if(c0&&cz.nav){d7(eC)}}dG=ey||true;eq.ok=true;eq()}}d3=cf.offset().left;i();return this};cg.setOptions=function(ex){bV.extend(c3,ex);dV();return this};cg.shuffle=function(){dP&&aC(dP)&&dV();return this};function co(ex,ey){if(ec){ex.removeClass(S+" "+aL);ex.removeClass(a2+" "+aR);ey&&!dg&&ex.addClass(ey.replace(/^|\s/g," "+bz+"--"))}}cg.longPress={threshold:1,count:0,thumbSlideTime:20,progress:function(){if(!this.inProgress){this.count++;this.inProgress=this.count>this.threshold}},end:function(){if(this.inProgress){this.isEnded=true}},reset:function(){this.count=0;this.inProgress=false;this.isEnded=false}};cg.destroy=function(){cg.cancelFullScreen();cg.stopAutoplay();dP=cg.data=null;dd();ea=[];ce(bu);dV.ok=false;return this};cg.playVideo=function(){var ez=dF,ex=ez.video,ey=eo;if(typeof ex==="object"&&ez.videoReady){d2&&cg.fullScreen&&cg.cancelFullScreen();bX(function(){return !bB.is()||ey!==eo},function(){if(ey===eo){ez.$video=ez.$video||bV(ab(bJ)).append(q(ex));ez.$video.appendTo(ez[bu]);dk.addClass(bt);dg=ez.$video;em();cU.blur();dM.blur();dc("loadvideo")}})}return this};cg.stopVideo=function(){cO(dg,true,true);return this};cg.spliceByIndex=function(ex,ey){ey.i=ex+1;ey.img&&bV.ajax({url:ey.img,type:"HEAD",success:function(){dP.splice(ex,1,ey);dV()}})};function cO(ex,ez,ey){if(ez){dk.removeClass(bt);dg=false;em()}if(ex&&ex!==dg){ex.remove();dc("unloadvideo")}if(ey){cw();es()}}function cp(ex){dk.toggleClass(P,ex)}function ei(ez){if(d5.flow){return}var ex=ez?ez.pageX:ei.x,ey=ex&&!cE(eh(ex))&&c3.click;if(ei.p!==ey&&cf.toggleClass(bC,ey)){ei.p=ey;ei.x=ex}}cf.on("mousemove",ei);function cm(ex){clearTimeout(cm.t);if(c3.clicktransition&&c3.clicktransition!==c3.transition){setTimeout(function(){var ey=c3.transition;cg.setOptions({transition:c3.clicktransition});cu=ey;cm.t=setTimeout(function(){cg.show(ex)},10)},0)}else{cg.show(ex)}}function ct(eA,ey){var ez=eA.target,ex=bV(ez);if(ex.hasClass(T)){cg.playVideo()}else{if(ez===dD){cg.toggleFullScreen()}else{if(dg){ez===d1&&cO(dg,true,true)}else{if(!d6.hasClass(M)){cg.requestFullScreen()}}}}O(eA,true)}function cD(ex,ey){d5[ex]=dy[ex]=ey}d5=ao(cl,{onStart:dR,onMove:function(ey,ex){co(cf,ex.edge)},onTouchEnd:cr,onEnd:function(ex){var ez;co(cf);ez=(aZ&&!dz||ex.touch)&&c3.arrows;if((ex.moved||(ez&&ex.pos!==ex.newPos&&!ex.control))&&ex.$target[0]!==dM[0]){var ey=by(ex.newPos,en.w,c3.margin,c2);cg.show({index:ey,time:cv?ds:ex.time,overPos:ex.overPos,user:true})}else{if(!ex.aborted&&!ex.control){ct(ex.startEvent,ez)}}},timeLow:1,timeHigh:1,friction:2,select:"."+a9+", ."+a9+" *",$wrap:cf,direction:"horizontal"});dy=ao(cF,{onStart:dR,onMove:function(ey,ex){co(dO,ex.edge)},onTouchEnd:cr,onEnd:function(ex){function ey(){dT.l=ex.newPos;cw();es();dp(ex.newPos,true);ev()}if(!ex.moved){var ez=ex.$target.closest("."+aG,cF)[0];ez&&dJ.call(ez,ex.startEvent)}else{if(ex.pos!==ex.newPos){cj=true;A(cF,{time:ex.time,pos:ex.newPos,overPos:ex.overPos,direction:c3.navdir,onEnd:ey});dp(ex.newPos);ec&&co(dO,bp(ex.newPos,dy.min,dy.max,ex.dir))}else{ey()}}},timeLow:0.5,timeHigh:2,friction:5,$wrap:dO,direction:c3.navdir});cG=o(cf,{shift:true,onEnd:function(ey,ex){dR();cr();cg.show({index:ex,slow:ey.altKey})}});ef=o(dO,{onEnd:function(ez,ey){dR();cr();var ex=a1(cF)+ey*0.25;cF.css(b2(bb(ex,dy.min,dy.max),c3.navdir));ec&&co(dO,bp(ex,dy.min,dy.max,c3.navdir));ef.prevent={"<":ex>=dy.max,">":ex<=dy.min};clearTimeout(ef.t);ef.t=setTimeout(function(){dT.l=ex;dp(ex,true)},b8);dp(ex)}});dk.hover(function(){setTimeout(function(){if(c6){return}cp(!(dz=true))},0)},function(){if(!dz){return}cp(!(dz=false))});function dJ(ey){var ex=bV(this).data().eq;if(c3.navtype==="thumbs"){cm({index:ex,slow:ey.altKey,user:true,coo:ey._x-dO.offset().left})}else{cm({index:ex,slow:ey.altKey,user:true})}}function et(ex){cm({index:cU.index(this)?">":"<",slow:ex.altKey,user:true})}z(cU,function(ex){O(ex);et.call(this,ex)},{onStart:function(){dR();d5.control=true},onTouchEnd:cr});z(eg,function(ex){O(ex);if(c3.navtype==="thumbs"){cg.show("<")}else{cg.showSlide("prev")}});z(dX,function(ex){O(ex);if(c3.navtype==="thumbs"){cg.show(">")}else{cg.showSlide("next")}});function dv(ex){bL(ex,function(){setTimeout(function(){bU(cf)},0);cp(false)})}cU.each(function(){a(this,function(ex){et.call(this,ex)});dv(this)});a(dD,function(){if(d6.hasClass(M)){cg.cancelFullScreen();cl.focus()}else{cg.requestFullScreen();dM.focus()}});dv(dD);function dV(){dn();dQ();if(!dV.i){dV.i=true;var ex=c3.startindex;eo=c2=ed=dw=dx=du(ex)||0}if(c1){if(cV()){return}if(dg){cO(dg,true)}ea=[];ce(bu);dV.ok=true;cg.show({index:eo,time:0});cg.resize()}else{cg.destroy()}}function cV(){if(!cV.f===dH){cV.f=dH;eo=c1-1-eo;cg.reverse();return true}}bV.each("load push pop shift unshift reverse sort splice".split(" "),function(ex,ey){cg[ey]=function(){dP=dP||[];if(ey!=="load"){Array.prototype[ey].apply(dP,arguments)}else{if(arguments[0]&&typeof arguments[0]==="object"&&arguments[0].length){dP=bG(arguments[0])}}dV();return cg}});function eq(){if(eq.ok){eq.ok=false;dc("ready")}}dV()};bV.fn.fotorama=function(i){return this.each(function(){var ce=this,cd=bV(this),cc=cd.data(),cf=cc.fotorama;if(!cf){bX(function(){return !W(ce)},function(){cc.urtext=cd.html();new bV.Fotorama(cd,bV.extend({},Q,bo.fotoramaDefaults,i,cc))})}else{cf.setOptions(i,true)}})};bV.Fotorama.instances=[];function b1(){bV.each(bV.Fotorama.instances,function(cc,i){i.index=cc})}function C(i){bV.Fotorama.instances.push(i);b1()}function av(i){bV.Fotorama.instances.splice(i.index,1);b1()}bV.Fotorama.cache={};bV.Fotorama.measures={};bV=bV||{};bV.Fotorama=bV.Fotorama||{};bV.Fotorama.jst=bV.Fotorama.jst||{};bV.Fotorama.jst.dots=function(cc){var i,ce="",cd=bx.escape;ce+='<div class="fotorama__nav__frame fotorama__nav__frame--dot" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__dot"></div>\r\n</div>';return ce};bV.Fotorama.jst.frameCaption=function(cc){var i,ce="",cd=bx.escape;ce+='<div class="fotorama__caption" aria-hidden="true">\r\n <div class="fotorama__caption__wrap" id="'+((i=(cc.labelledby))==null?"":i)+'">'+((i=(cc.caption))==null?"":i)+"</div>\r\n</div>\r\n";return ce};bV.Fotorama.jst.style=function(cc){var i,ce="",cd=bx.escape;ce+=".fotorama"+((i=(cc.s))==null?"":i)+" .fotorama__nav--thumbs .fotorama__nav__frame{\r\npadding:"+((i=(cc.m))==null?"":i)+"px;\r\nheight:"+((i=(cc.h))==null?"":i)+"px}\r\n.fotorama"+((i=(cc.s))==null?"":i)+" .fotorama__thumb-border{\r\nheight:"+((i=(cc.h))==null?"":i)+"px;\r\nborder-width:"+((i=(cc.b))==null?"":i)+"px;\r\nmargin-top:"+((i=(cc.m))==null?"":i)+"px}";return ce};bV.Fotorama.jst.thumb=function(cc){var i,ce="",cd=bx.escape;ce+='<div class="fotorama__nav__frame fotorama__nav__frame--thumb" tabindex="0" role="button" data-gallery-role="nav-frame" data-nav-type="thumb" aria-label>\r\n <div class="fotorama__thumb">\r\n </div>\r\n</div>';return ce}})(window,document,location,typeof jQuery!=="undefined"&&jQuery); diff --git a/lib/web/i18n/en_US.csv b/lib/web/i18n/en_US.csv index 4acc62aa6dc81..7938068871963 100644 --- a/lib/web/i18n/en_US.csv +++ b/lib/web/i18n/en_US.csv @@ -27,6 +27,7 @@ Submit,Submit "Letters, numbers, spaces or underscores only please","Letters, numbers, spaces or underscores only please" "Letters only please","Letters only please" "No white space please","No white space please" +"No marginal white space please","No marginal white space please" "Your ZIP-code must be in the range 902xx-xxxx to 905-xx-xxxx","Your ZIP-code must be in the range 902xx-xxxx to 905-xx-xxxx" "A positive or negative non-decimal number please","A positive or negative non-decimal number please" "The specified vehicle identification number (VIN) is invalid.","The specified vehicle identification number (VIN) is invalid." diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js index 48960ed3403c0..98ce6a005db04 100644 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/html5-schema.js @@ -186,11 +186,19 @@ define([ }); _.each(compiled, function (node, nodeName) { - var attributes = node.attributes.join('|'), - children = node.children.join('|'); + var filteredAttributes = []; - validElements.push(nodeName + '[' + attributes + ']'); - validChildren.push(nodeName + '[' + children + ']'); + _.each(node.attributes, function (attribute) { //eslint-disable-line max-nested-callbacks + // Disallowing usage of 'on*' attributes. + if (!/^on/.test(attribute)) { + filteredAttributes.push(attribute); + } + }); + + node.attributes = filteredAttributes; + + validElements.push(nodeName + '[' + node.attributes.join('|') + ']'); + validChildren.push(nodeName + '[' + node.children.join('|') + ']'); }); return { diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js index f2b3365555f3c..ed105bd2c18f5 100755 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js @@ -461,7 +461,12 @@ define([ decodeDirectives: function (content) { // escape special chars in directives url to use it in regular expression var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'), - reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)')); + reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)')), + uriReg = /___directive\/(.*)\/key\//g; + + content = content.replace(uriReg, function (match) { + return decodeURIComponent(match); + }); return content.gsub(reg, function (match) { //eslint-disable-line no-extra-bind return Base64.mageDecode(match[1]); diff --git a/lib/web/mage/collapsible.js b/lib/web/mage/collapsible.js index 656afc8f89e25..2262c1f74fbe7 100644 --- a/lib/web/mage/collapsible.js +++ b/lib/web/mage/collapsible.js @@ -444,14 +444,19 @@ define([ * Activate. */ activate: function () { - if (!this.options.disabled) { - if (this.options.animate) { - this._animate(showProps); - } else { - this.content.show(); + if (this.options.disabled) { + return; + } + + if (this.options.animate) { + this._animate(showProps); + } else { + if (this.content.length) { + this._scrollToTopIfVisible(this.content.get(0).parentElement); } - this._open(); + this.content.show(); } + this._open(); }, /** @@ -553,6 +558,32 @@ define([ }, 1); }); } + }, + + /** + * @param {HTMLElement} elem + * @private + */ + _scrollToTopIfVisible: function (elem) { + if (this._isElementOutOfViewport(elem)) { + elem.scrollIntoView(); + } + }, + + /** + * @param {HTMLElement} elem + * @private + * @return {Boolean} + */ + _isElementOutOfViewport: function (elem) { + var rect = elem.getBoundingClientRect(); + + return ( + rect.left < 0 || + rect.top < 0 || + rect.right > window.innerWidth || + rect.bottom > window.innerHeight + ); } }); diff --git a/lib/web/mage/menu.js b/lib/web/mage/menu.js index 41730aa036b08..e4ccbdf325ecc 100644 --- a/lib/web/mage/menu.js +++ b/lib/web/mage/menu.js @@ -85,12 +85,10 @@ define([ var controls = this.controls, toggle = this.toggle; - this._on(controls.toggleBtn, { - 'click': toggle - }); - this._on(controls.swipeArea, { - 'swipeleft': toggle - }); + controls.toggleBtn.off('click'); + controls.toggleBtn.on('click', toggle.bind(this)); + controls.swipeArea.off('swipeleft'); + controls.swipeArea.on('swipeleft', toggle.bind(this)); }, /** @@ -481,6 +479,8 @@ define([ _toggleDesktopMode: function () { var categoryParent, html; + $(this.element).off('mouseenter mouseleave click'); + this._on({ /** * Prevent focus from sticking to links inside menu after clicking diff --git a/lib/web/mage/requirejs/resolver.js b/lib/web/mage/requirejs/resolver.js index 6927a3205d94d..588a0f8411cff 100644 --- a/lib/web/mage/requirejs/resolver.js +++ b/lib/web/mage/requirejs/resolver.js @@ -28,13 +28,13 @@ define([ } /** - * Checks if provided module is registered after load. + * Checks if provided module is rejected during load. * * @param {Object} module - Module to be checked. * @return {Boolean} */ - function isRegistered(module) { - return registry[module.id]; + function isRejected(module) { + return registry[module.id] && registry[module.id].error; } /** @@ -48,7 +48,7 @@ define([ return false; } - return module.depCount > _.filter(module.depMaps, isRegistered).length; + return module.depCount > _.filter(module.depMaps, isRejected).length; } /** diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index 5f088f0446bb5..e14ecf982bc96 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -253,6 +253,12 @@ }, $.mage.__('No white space please') ], + 'no-marginal-whitespace': [ + function (value, element) { + return this.optional(element) || !/^\s+|\s+$/i.test(value); + }, + $.mage.__('No marginal white space please') + ], 'zip-range': [ function (value, element) { return this.optional(element) || /^90[2-5]-\d{2}-\d{4}$/.test(value); diff --git a/lib/web/magnifier/magnifier.js b/lib/web/magnifier/magnifier.js index 150c8adf0b22b..0807a4c394995 100644 --- a/lib/web/magnifier/magnifier.js +++ b/lib/web/magnifier/magnifier.js @@ -554,6 +554,15 @@ thumbObj.src = thumb.src; } + /** + * Hide magnifier when mouse exceeds image bounds. + */ + function onMouseLeave() { + onThumbLeave(); + isOverThumb = false; + $magnifierPreview.addClass(MagnifyCls.magnifyHidden); + } + function onMousemove(e) { pos.x = e.clientX; pos.y = e.clientY; @@ -564,15 +573,9 @@ isOverThumb = inBounds; } - if (inBounds && isOverThumb) { - if(gMode === 'outside'){ - $magnifierPreview.removeClass(MagnifyCls.magnifyHidden); - } + if (inBounds && isOverThumb && gMode === 'outside') { + $magnifierPreview.removeClass(MagnifyCls.magnifyHidden); move(); - } else { - onThumbLeave(); - isOverThumb = false; - $magnifierPreview.addClass(MagnifyCls.magnifyHidden); } } @@ -589,6 +592,8 @@ }); $box.on('mousemove', onMousemove); + $box.on('mouseleave', onMouseLeave); + _init($box, customUserOptions); } }(jQuery)); diff --git a/pub/errors/processor.php b/pub/errors/processor.php index 7240707f642c2..88e9d2bf5294c 100644 --- a/pub/errors/processor.php +++ b/pub/errors/processor.php @@ -268,10 +268,11 @@ public function getHostUrl() $isSecure = (!empty($_SERVER['HTTPS'])) && ($_SERVER['HTTPS'] != 'off'); $url = ($isSecure ? 'https://' : 'http://') . $host; - if (!empty($_SERVER['SERVER_PORT']) && !in_array($_SERVER['SERVER_PORT'], [80, 443]) + $port = explode(':', $host); + if (isset($port[1]) && !in_array($port[1], [80, 443]) && !preg_match('/.*?\:[0-9]+$/', $url) ) { - $url .= ':' . $_SERVER['SERVER_PORT']; + $url .= ':' . $port[1]; } return $url; } @@ -379,6 +380,8 @@ protected function _loadXml($xmlFile) } /** + * Render page + * * @param string $template * @return string */ @@ -463,7 +466,7 @@ protected function _setReportData($reportData) public function saveReport($reportData) { $this->reportData = $reportData; - $this->reportId = abs(intval(microtime(true) * random_int(100, 1000))); + $this->reportId = abs((int)microtime(true) * random_int(100, 1000)); $this->_reportFile = $this->_reportDir . '/' . $this->reportId; $this->_setReportData($reportData); diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx index 8f8a72673d858..f4f3f3052bb68 100644 --- a/setup/performance-toolkit/benchmark.jmx +++ b/setup/performance-toolkit/benchmark.jmx @@ -2535,21 +2535,21 @@ if (props.get("category_names_list") == null) { <elementProp name="HTTPsampler.Arguments" elementType="Arguments" guiclass="HTTPArgumentsPanel" testclass="Arguments" enabled="true"> <collectionProp name="Arguments.arguments"> <elementProp name="blocks" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> + <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">["customer_form_login"]</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">blocks</stringProp> </elementProp> <elementProp name="handles" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> + <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">["default","customer_account_login"]</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">handles</stringProp> </elementProp> <elementProp name="originalRequest" elementType="HTTPArgument"> - <boolProp name="HTTPArgument.always_encode">false</boolProp> + <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">{"route":"customer","controller":"account","action":"login","uri":"/customer/account/login/"}</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> @@ -2622,20 +2622,23 @@ if (props.get("category_names_list") == null) { <stringProp name="filename"/> <stringProp name="cacheKey"/> <stringProp name="script">var cacheHitPercent = vars.get("cache_hits_percentage"); - -if ( - cacheHitPercent < 100 && - sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' -) { - doCache(); -} - -function doCache(){ - var random = Math.random() * 100; - if (cacheHitPercent < random) { - sampler.setPath(sampler.getPath() + "?cacheModifier=" + Math.random().toString(36).substring(2, 13)); - } -} + vars.put("shouldBeCached", true); + + if ( + cacheHitPercent < 100 && + sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' + ) { + doCache(); + } + + function doCache(){ + var random = Math.random() * 100; + + if (cacheHitPercent < random) { + sampler.setPath(sampler.getPath() + "?cacheModifier=" + Math.random().toString(36).substring(2, 13)); + vars.put("shouldBeCached", false); + } + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/cache_hit_miss.jmx</stringProp></JSR223PreProcessor> <hashTree/> @@ -2876,12 +2879,12 @@ vars.put("customer_email", customerUser); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -2938,6 +2941,25 @@ vars.put("customer_email", customerUser); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Category" enabled="true"> @@ -2990,6 +3012,25 @@ vars.put("customer_email", customerUser); <stringProp name="Scope.variable">category_id</stringProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="View Simple Products" enabled="true"> @@ -3057,6 +3098,25 @@ vars.put("product_sku", product.get("sku")); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> </hashTree> @@ -3125,6 +3185,25 @@ vars.put("product_sku", product.get("sku")); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> </hashTree> @@ -3291,6 +3370,25 @@ vars.put("category_name", props.get("category_names_list").get(number)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Category" enabled="true"> @@ -3343,6 +3441,25 @@ vars.put("category_name", props.get("category_names_list").get(number)); <stringProp name="Scope.variable">category_id</stringProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="View Simple Products" enabled="true"> @@ -3410,6 +3527,25 @@ vars.put("product_sku", product.get("sku")); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> </hashTree> @@ -3478,6 +3614,25 @@ vars.put("product_sku", product.get("sku")); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> </hashTree> </hashTree> @@ -3532,20 +3687,23 @@ if (testLabel <stringProp name="filename"/> <stringProp name="cacheKey"/> <stringProp name="script">var cacheHitPercent = vars.get("cache_hits_percentage"); - -if ( - cacheHitPercent < 100 && - sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' -) { - doCache(); -} - -function doCache(){ - var random = Math.random() * 100; - if (cacheHitPercent < random) { - sampler.setPath(sampler.getPath() + "?cacheModifier=" + Math.random().toString(36).substring(2, 13)); - } -} + vars.put("shouldBeCached", true); + + if ( + cacheHitPercent < 100 && + sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy' + ) { + doCache(); + } + + function doCache(){ + var random = Math.random() * 100; + + if (cacheHitPercent < random) { + sampler.setPath(sampler.getPath() + "?cacheModifier=" + Math.random().toString(36).substring(2, 13)); + vars.put("shouldBeCached", false); + } + } </stringProp> <stringProp name="TestPlan.comments">mpaf/tool/fragments/ce/common/cache_hit_miss.jmx</stringProp></JSR223PreProcessor> <hashTree/> @@ -3635,6 +3793,25 @@ if (testLabel <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Search" enabled="true"> @@ -3816,6 +3993,25 @@ vars.put("product_url_key", product); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> </hashTree> </hashTree> @@ -3906,6 +4102,25 @@ if (testLabel <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Search" enabled="true"> @@ -4240,6 +4455,25 @@ vars.put("product_url_key", product); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> </hashTree> </hashTree> @@ -4330,6 +4564,25 @@ if (testLabel <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> @@ -4559,6 +4812,25 @@ vars.put("product_url_key", product); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> </hashTree> </hashTree> @@ -4696,6 +4968,25 @@ vars.put("category_name", props.get("category_names_list").get(number)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Category" enabled="true"> @@ -4748,6 +5039,25 @@ vars.put("category_name", props.get("category_names_list").get(number)); <stringProp name="Scope.variable">category_id</stringProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Add Simple Products to Cart" enabled="true"> @@ -4829,6 +5139,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Simple Product ${_counter} Add To Cart" enabled="true"> @@ -4901,12 +5230,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -5052,6 +5381,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="SetUp - Get Configurable Product Options" enabled="true"> @@ -5267,12 +5615,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -5561,12 +5909,12 @@ vars.put("customer_email", customerUser); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -5659,6 +6007,25 @@ vars.put("product_sku", product.get("sku")); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Simple Product ${_counter} Add To Wishlist" enabled="true"> @@ -5734,12 +6101,12 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -6030,6 +6397,25 @@ vars.put("category_name", props.get("category_names_list").get(number)); <stringProp name="Scope.variable">random_product_compare_id</stringProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Add Simple Products to Compare" enabled="true"> @@ -6111,6 +6497,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Simple Product ${_counter} Comparison Add" enabled="true"> @@ -6166,12 +6571,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -6289,6 +6694,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Configurable Product ${_counter} Comparison Add" enabled="true"> @@ -6344,12 +6768,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -6577,6 +7001,25 @@ vars.put("category_name", props.get("category_names_list").get(number)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Category" enabled="true"> @@ -6629,6 +7072,25 @@ vars.put("category_name", props.get("category_names_list").get(number)); <stringProp name="Scope.variable">category_id</stringProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Add Simple Products to Cart" enabled="true"> @@ -6710,6 +7172,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Simple Product ${_counter} Add To Cart" enabled="true"> @@ -6782,12 +7263,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -6933,6 +7414,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="SetUp - Get Configurable Product Options" enabled="true"> @@ -7148,12 +7648,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -7742,6 +8242,25 @@ vars.put("customer_email", customerUser); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Login Page" enabled="true"> @@ -7864,12 +8383,12 @@ vars.put("customer_email", customerUser); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -7947,6 +8466,25 @@ vars.put("customer_email", customerUser); <stringProp name="Scope.variable">category_id</stringProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Add Simple Products to Cart" enabled="true"> @@ -8028,6 +8566,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Simple Product ${_counter} Add To Cart" enabled="true"> @@ -8100,12 +8657,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -8251,6 +8808,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="SetUp - Get Configurable Product Options" enabled="true"> @@ -8466,12 +9042,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -9141,12 +9717,12 @@ vars.put("customer_email", customerUser); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -9239,6 +9815,25 @@ vars.put("product_sku", product.get("sku")); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Simple Product 1 Rate and Review" enabled="true"> @@ -9325,12 +9920,12 @@ vars.put("product_sku", product.get("sku")); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -9657,12 +10252,12 @@ vars.put("customer_email", customerUser); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -9719,6 +10314,25 @@ vars.put("customer_email", customerUser); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Category" enabled="true"> @@ -9771,6 +10385,25 @@ vars.put("customer_email", customerUser); <stringProp name="Scope.variable">category_id</stringProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="Add Simple Products to Cart" enabled="true"> @@ -9852,6 +10485,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Simple Product ${_counter} Add To Cart" enabled="true"> @@ -9924,12 +10576,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -10075,6 +10727,25 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <LoopController guiclass="LoopControlPanel" testclass="LoopController" testname="SetUp - Get Configurable Product Options" enabled="true"> @@ -10290,12 +10961,12 @@ vars.put("totalProductsAdded", String.valueOf(productsAdded)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -10487,12 +11158,12 @@ vars.put("item_id", vars.get("cart_items_qty_inputs_" + id)); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> @@ -10686,6 +11357,25 @@ vars.put("customer_email", customerUser); <intProp name="Assertion.test_type">2</intProp> </ResponseAssertion> <hashTree/> + <BeanShellAssertion guiclass="BeanShellAssertionGui" testclass="BeanShellAssertion" testname="Cache mis assertion" enabled="true"> +<stringProp name="BeanShellAssertion.query">int cacheHitPercent = Integer.parseInt(vars.get("cache_hits_percentage")); + + if (cacheHitPercent < 100) { + + shouldBeCached = vars.get("shouldBeCached").toString(); + pageIsCached = (ResponseHeaders.contains("X-Cache: MISS, HIT") || ResponseHeaders.contains("X-Cache: HIT")).toString(); + + if ((pageIsCached != shouldBeCached) && (shouldBeCached == "false")) { + Failure = true; + FailureMessage = "This request should be cached but it isn't"; + } + } + </stringProp> + <stringProp name="BeanShellAssertion.filename"/> + <stringProp name="BeanShellAssertion.parameters"/> + <boolProp name="BeanShellAssertion.resetInterpreter">false</boolProp> + </BeanShellAssertion> + <hashTree/> </hashTree> <HTTPSamplerProxy guiclass="HttpTestSampleGui" testclass="HTTPSamplerProxy" testname="Open Login Page" enabled="true"> @@ -10808,12 +11498,12 @@ vars.put("customer_email", customerUser); <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">false</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> diff --git a/setup/performance-toolkit/benchmark_2015.jmx b/setup/performance-toolkit/benchmark_2015.jmx index 8be7749a08bc4..e58e610304199 100644 --- a/setup/performance-toolkit/benchmark_2015.jmx +++ b/setup/performance-toolkit/benchmark_2015.jmx @@ -2981,12 +2981,12 @@ vars.put("loadType", "Guest");</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> @@ -3217,12 +3217,12 @@ vars.put("loadType", "Guest");</stringProp> <stringProp name="Argument.name">sections</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> @@ -3565,12 +3565,12 @@ vars.put("loadType", "Guest");</stringProp> <stringProp name="Argument.name">sections</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> @@ -4039,12 +4039,12 @@ vars.put("loadType", "Guest");</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> @@ -4275,12 +4275,12 @@ vars.put("loadType", "Guest");</stringProp> <stringProp name="Argument.name">sections</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> @@ -4623,12 +4623,12 @@ vars.put("loadType", "Guest");</stringProp> <stringProp name="Argument.name">sections</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> @@ -5637,12 +5637,12 @@ vars.put("loadType", "Customer");</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> <stringProp name="Argument.name">sections</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">false</boolProp> @@ -5873,12 +5873,12 @@ vars.put("loadType", "Customer");</stringProp> <stringProp name="Argument.name">sections</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> @@ -6221,12 +6221,12 @@ vars.put("loadType", "Customer");</stringProp> <stringProp name="Argument.name">sections</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> - <elementProp name="update_section_id" elementType="HTTPArgument"> + <elementProp name="force_new_section_timestamp" elementType="HTTPArgument"> <boolProp name="HTTPArgument.always_encode">true</boolProp> <stringProp name="Argument.value">true</stringProp> <stringProp name="Argument.metadata">=</stringProp> <boolProp name="HTTPArgument.use_equals">true</boolProp> - <stringProp name="Argument.name">update_section_id</stringProp> + <stringProp name="Argument.name">force_new_section_timestamp</stringProp> <stringProp name="Argument.desc">true</stringProp> </elementProp> <elementProp name="_" elementType="HTTPArgument"> diff --git a/setup/performance-toolkit/config/description.xml b/setup/performance-toolkit/config/description.xml index ae7e1e343b1da..06fa6a2618f68 100644 --- a/setup/performance-toolkit/config/description.xml +++ b/setup/performance-toolkit/config/description.xml @@ -7,16 +7,16 @@ --> <description> <paragraphs> - <count-min>4</count-min> - <count-max>10</count-max> + <count-min>7</count-min> + <count-max>7</count-max> <sentences> - <count-min>10</count-min> - <count-max>15</count-max> + <count-min>12</count-min> + <count-max>12</count-max> <words> - <count-min>5</count-min> - <count-max>7</count-max> + <count-min>6</count-min> + <count-max>6</count-max> </words> </sentences> </paragraphs> diff --git a/setup/performance-toolkit/profiles/ce/extra_large.xml b/setup/performance-toolkit/profiles/ce/extra_large.xml index 3563fcbcf9cf1..390bf7fb12003 100644 --- a/setup/performance-toolkit/profiles/ce/extra_large.xml +++ b/setup/performance-toolkit/profiles/ce/extra_large.xml @@ -23,9 +23,6 @@ <attribute> <options>8</options> </attribute> - <attribute> - <options>5</options> - </attribute> </attributes> <sku>Configurable Product %s</sku> <products>16000</products> @@ -43,9 +40,9 @@ <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> - <product_attribute_sets>200</product_attribute_sets> <!-- Number of product attribute sets --> - <product_attribute_sets_attributes>50</product_attribute_sets_attributes> <!-- Number of attributes per set --> - <product_attribute_sets_attributes_values>2</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> + <product_attribute_sets>100</product_attribute_sets> <!-- Number of product attribute sets --> + <product_attribute_sets_attributes>30</product_attribute_sets_attributes> <!-- Number of attributes per set --> + <product_attribute_sets_attributes_values>15</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> <order_quotes_enable>true</order_quotes_enable> <order_simple_product_count_from>2</order_simple_product_count_from> diff --git a/setup/performance-toolkit/profiles/ce/large.xml b/setup/performance-toolkit/profiles/ce/large.xml index 0d3cd7ff6b8f4..ed91b22930af5 100644 --- a/setup/performance-toolkit/profiles/ce/large.xml +++ b/setup/performance-toolkit/profiles/ce/large.xml @@ -23,9 +23,6 @@ <attribute> <options>8</options> </attribute> - <attribute> - <options>5</options> - </attribute> </attributes> <sku>Configurable Product %s</sku> <products>8000</products> @@ -43,9 +40,9 @@ <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> - <product_attribute_sets>200</product_attribute_sets> <!-- Number of product attribute sets --> - <product_attribute_sets_attributes>50</product_attribute_sets_attributes> <!-- Number of attributes per set --> - <product_attribute_sets_attributes_values>2</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> + <product_attribute_sets>50</product_attribute_sets> <!-- Number of product attribute sets --> + <product_attribute_sets_attributes>20</product_attribute_sets_attributes> <!-- Number of attributes per set --> + <product_attribute_sets_attributes_values>15</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> <order_quotes_enable>true</order_quotes_enable> <order_simple_product_count_from>2</order_simple_product_count_from> diff --git a/setup/performance-toolkit/profiles/ce/medium.xml b/setup/performance-toolkit/profiles/ce/medium.xml index e08beb2f4b056..f01eabb7898f3 100644 --- a/setup/performance-toolkit/profiles/ce/medium.xml +++ b/setup/performance-toolkit/profiles/ce/medium.xml @@ -23,9 +23,6 @@ <attribute> <options>8</options> </attribute> - <attribute> - <options>5</options> - </attribute> </attributes> <sku>Configurable Product %s</sku> <products>640</products> @@ -43,9 +40,9 @@ <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> - <product_attribute_sets>100</product_attribute_sets> <!-- Number of product attribute sets --> - <product_attribute_sets_attributes>50</product_attribute_sets_attributes> <!-- Number of attributes per set --> - <product_attribute_sets_attributes_values>2</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> + <product_attribute_sets>30</product_attribute_sets> <!-- Number of product attribute sets --> + <product_attribute_sets_attributes>10</product_attribute_sets_attributes> <!-- Number of attributes per set --> + <product_attribute_sets_attributes_values>8</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> <order_quotes_enable>true</order_quotes_enable> <order_simple_product_count_from>2</order_simple_product_count_from> diff --git a/setup/performance-toolkit/profiles/ce/medium_msite.xml b/setup/performance-toolkit/profiles/ce/medium_msite.xml index 87bde135817db..a57fcad0779fe 100644 --- a/setup/performance-toolkit/profiles/ce/medium_msite.xml +++ b/setup/performance-toolkit/profiles/ce/medium_msite.xml @@ -23,9 +23,6 @@ <attribute> <options>8</options> </attribute> - <attribute> - <options>5</options> - </attribute> </attributes> <sku>Configurable Product %s</sku> <products>800</products> @@ -49,9 +46,9 @@ <cart_price_rules>20</cart_price_rules> <!-- Number of cart price rules --> <cart_price_rules_floor>2</cart_price_rules_floor> - <product_attribute_sets>100</product_attribute_sets> <!-- Number of product attribute sets --> - <product_attribute_sets_attributes>50</product_attribute_sets_attributes> <!-- Number of attributes per set --> - <product_attribute_sets_attributes_values>2</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> + <product_attribute_sets>30</product_attribute_sets> <!-- Number of product attribute sets --> + <product_attribute_sets_attributes>10</product_attribute_sets_attributes> <!-- Number of attributes per set --> + <product_attribute_sets_attributes_values>8</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> <order_quotes_enable>true</order_quotes_enable> <order_simple_product_count_from>2</order_simple_product_count_from> diff --git a/setup/performance-toolkit/profiles/ce/small.xml b/setup/performance-toolkit/profiles/ce/small.xml index f3a1169bd9658..60ae901d8f5e0 100644 --- a/setup/performance-toolkit/profiles/ce/small.xml +++ b/setup/performance-toolkit/profiles/ce/small.xml @@ -23,9 +23,6 @@ <attribute> <options>8</options> </attribute> - <attribute> - <options>5</options> - </attribute> </attributes> <sku>Configurable Product %s</sku> <products>16</products> @@ -44,8 +41,8 @@ <cart_price_rules_floor>2</cart_price_rules_floor> <product_attribute_sets>10</product_attribute_sets> <!-- Number of product attribute sets --> - <product_attribute_sets_attributes>10</product_attribute_sets_attributes> <!-- Number of attributes per set --> - <product_attribute_sets_attributes_values>2</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> + <product_attribute_sets_attributes>5</product_attribute_sets_attributes> <!-- Number of attributes per set --> + <product_attribute_sets_attributes_values>5</product_attribute_sets_attributes_values> <!-- Number of values per attribute --> <order_quotes_enable>true</order_quotes_enable> <order_simple_product_count_from>2</order_simple_product_count_from> diff --git a/setup/src/Magento/Setup/Model/PhpReadinessCheck.php b/setup/src/Magento/Setup/Model/PhpReadinessCheck.php index 2c4967c4c2ffd..c73c13129dfa1 100644 --- a/setup/src/Magento/Setup/Model/PhpReadinessCheck.php +++ b/setup/src/Magento/Setup/Model/PhpReadinessCheck.php @@ -192,7 +192,7 @@ public function checkMemoryLimit() $currentMemoryLimit = ini_get('memory_limit'); - $currentMemoryInteger = intval($currentMemoryLimit); + $currentMemoryInteger = (int)$currentMemoryLimit; if ($currentMemoryInteger > 0 && $this->dataSize->convertSizeToBytes($currentMemoryLimit) @@ -236,6 +236,7 @@ public function checkMemoryLimit() /** * Checks if xdebug.max_nesting_level is set 200 or more * @return array + * @SuppressWarnings(PHPMD.LongVariable) */ private function checkXDebugNestedLevel() { @@ -244,7 +245,7 @@ private function checkXDebugNestedLevel() $currentExtensions = $this->phpInformation->getCurrent(); if (in_array('xdebug', $currentExtensions)) { - $currentXDebugNestingLevel = intval(ini_get('xdebug.max_nesting_level')); + $currentXDebugNestingLevel = (int)ini_get('xdebug.max_nesting_level'); $minimumRequiredXDebugNestedLevel = $this->phpInformation->getRequiredMinimumXDebugNestedLevel(); if ($minimumRequiredXDebugNestedLevel > $currentXDebugNestingLevel) { @@ -286,7 +287,7 @@ private function checkPopulateRawPostSetting() $data = []; $error = false; - $iniSetting = intval(ini_get('always_populate_raw_post_data')); + $iniSetting = (int)ini_get('always_populate_raw_post_data'); $checkVersionConstraint = $this->versionParser->parseConstraints('~5.6.0'); $normalizedPhpVersion = $this->getNormalizedCurrentPhpVersion(PHP_VERSION); @@ -297,12 +298,12 @@ private function checkPopulateRawPostSetting() $message = sprintf( 'Your PHP Version is %s, but always_populate_raw_post_data = %d. - $HTTP_RAW_POST_DATA is deprecated from PHP 5.6 onwards and will be removed in PHP 7.0. - This will stop the installer from running. - Please open your php.ini file and set always_populate_raw_post_data to -1. - If you need more help please call your hosting provider.', + $HTTP_RAW_POST_DATA is deprecated from PHP 5.6 onwards and will be removed in PHP 7.0. + This will stop the installer from running. + Please open your php.ini file and set always_populate_raw_post_data to -1. + If you need more help please call your hosting provider.', PHP_VERSION, - intval(ini_get('always_populate_raw_post_data')) + (int)ini_get('always_populate_raw_post_data') ); $data['always_populate_raw_post_data'] = [ diff --git a/setup/view/magento/setup/navigation/side-menu.phtml b/setup/view/magento/setup/navigation/side-menu.phtml index 58d12a4de448a..f34a6c7c72e69 100644 --- a/setup/view/magento/setup/navigation/side-menu.phtml +++ b/setup/view/magento/setup/navigation/side-menu.phtml @@ -20,12 +20,8 @@ ng-show="<?= implode( '&&', $expressions) ?>" > <nav class="admin__menu" ng-controller="mainController"> - <span - class="logo logo-static" - data-edition="Community Edition"> - <img class="logo-img" - src="./pub/images/logo.svg" - alt="Magento Admin Panel"> + <span class="logo logo-static" data-edition="Community Edition"> + <img class="logo-img" src="<?= $this->basePath() ?>/pub/images/logo.svg" alt="Magento Admin Panel"> </span> <ul id="nav" role="menubar"> <li class="item-home level-0" ng-class="{_active: $state.current.name === 'root.home'}"> diff --git a/vendor/.htaccess b/vendor/.htaccess index 707c26b075e16..b97408bad3f2e 100644 --- a/vendor/.htaccess +++ b/vendor/.htaccess @@ -5,4 +5,3 @@ <IfVersion >= 2.4> Require all denied </IfVersion> -