diff --git a/.gitignore b/.gitignore index 9269cefc51fa0178e49324a776bef8b575d7ac08..a5b85031db205be7a5ccdf34fbfb4ead11787617 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ atlassian* /lib/internal/flex/varien/.settings /node_modules /.grunt +/Gruntfile.js +/package.json /pub/media/*.* !/pub/media/.htaccess diff --git a/app/bootstrap.php b/app/bootstrap.php index ec60a1708dacc3d2238960e5b1c9e7c0da05cc92..c8e676cb69cffd5cf32de8f12f9f83987cc6619a 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -11,14 +11,14 @@ error_reporting(E_ALL); #ini_set('display_errors', 1); /* PHP version validation */ -if (!defined('PHP_VERSION_ID') || !(PHP_VERSION_ID >= 50600 && PHP_VERSION_ID < 50700 || PHP_VERSION_ID === 70002 || PHP_VERSION_ID === 70004 || PHP_VERSION_ID >= 70006)) { +if (!defined('PHP_VERSION_ID') || !(PHP_VERSION_ID >= 50605 && PHP_VERSION_ID < 50700 || PHP_VERSION_ID === 70002 || PHP_VERSION_ID === 70004 || PHP_VERSION_ID >= 70006)) { if (PHP_SAPI == 'cli') { - echo 'Magento supports PHP 5.6, 7.0.2, 7.0.4, and 7.0.6 or later. ' . + echo 'Magento supports PHP 5.6.5, 7.0.2, 7.0.4, and 7.0.6 or later. ' . 'Please read http://devdocs.magento.com/guides/v1.0/install-gde/system-requirements.html'; } else { echo <<<HTML <div style="font:12px/1.35em arial, helvetica, sans-serif;"> - <p>Magento supports PHP 5.6, 7.0.2, 7.0.4, and 7.0.6 or later. Please read + <p>Magento supports PHP 5.6.5, 7.0.2, 7.0.4, and 7.0.6 or later. Please read <a target="_blank" href="http://devdocs.magento.com/guides/v1.0/install-gde/system-requirements.html"> Magento System Requirements</a>. </div> @@ -35,6 +35,17 @@ $umaskFile = BP . '/magento_umask'; $mask = file_exists($umaskFile) ? octdec(file_get_contents($umaskFile)) : 002; umask($mask); +if (empty($_SERVER['ENABLE_IIS_REWRITES']) || ($_SERVER['ENABLE_IIS_REWRITES'] != 1)) { + /* + * Unset headers used by IIS URL rewrites. + */ + unset($_SERVER['HTTP_X_REWRITE_URL']); + unset($_SERVER['HTTP_X_ORIGINAL_URL']); + unset($_SERVER['IIS_WasUrlRewritten']); + unset($_SERVER['UNENCODED_URL']); + unset($_SERVER['ORIG_PATH_INFO']); +} + if (!empty($_SERVER['MAGE_PROFILER']) && isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'text/html') !== false diff --git a/app/code/Magento/AdminNotification/composer.json b/app/code/Magento/AdminNotification/composer.json index 527268df36b41efe5078528138342a03fa33745d..09d8ce41bbb03607a2f8cd16831454c8565a5204 100644 --- a/app/code/Magento/AdminNotification/composer.json +++ b/app/code/Magento/AdminNotification/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-admin-notification", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-media-storage": "100.2.*", diff --git a/app/code/Magento/AdvancedPricingImportExport/composer.json b/app/code/Magento/AdvancedPricingImportExport/composer.json index 65ea7524dffff5bb778aaf5ec7658302072cfaf9..2fc465fa3c3dfd7a51abdd713f0c5c6dfd6843c6 100644 --- a/app/code/Magento/AdvancedPricingImportExport/composer.json +++ b/app/code/Magento/AdvancedPricingImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-advanced-pricing-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-catalog-inventory": "100.2.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/Authorization/composer.json b/app/code/Magento/Authorization/composer.json index 0ca367d4854dfe5c35811d15aee4d718c0a4edb7..af88e8376dc756cb9e1f6180848df41e6fd8b5f3 100644 --- a/app/code/Magento/Authorization/composer.json +++ b/app/code/Magento/Authorization/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-authorization", "description": "Authorization module provides access to Magento ACL functionality.", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/app/code/Magento/Authorizenet/composer.json b/app/code/Magento/Authorizenet/composer.json index 0c9e9641b6076850310edd75a2a4ea5ed3ccd094..b93cb6688f56d826a8d981518612897aae6f1ac8 100644 --- a/app/code/Magento/Authorizenet/composer.json +++ b/app/code/Magento/Authorizenet/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-authorizenet", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-sales": "100.2.*", "magento/module-store": "100.2.*", "magento/module-quote": "100.2.*", diff --git a/app/code/Magento/Authorizenet/i18n/en_US.csv b/app/code/Magento/Authorizenet/i18n/en_US.csv index 7183c706dc0a2ed264e7e73d0e11743cbf450b40..45025304c4e4423d77f60e1e26af4f2da6a8a569 100644 --- a/app/code/Magento/Authorizenet/i18n/en_US.csv +++ b/app/code/Magento/Authorizenet/i18n/en_US.csv @@ -64,3 +64,4 @@ Debug,Debug "Minimum Order Total","Minimum Order Total" "Maximum Order Total","Maximum Order Total" "Sort Order","Sort Order" +"Sorry, but something went wrong. Please contact the seller.","Sorry, but something went wrong. Please contact the seller." diff --git a/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js b/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js index cd05960c17633844fb1c57e92794066186a45965..86ec8e0c3922109170e19f4191fdbf7e27dc43d5 100644 --- a/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js +++ b/app/code/Magento/Authorizenet/view/frontend/web/js/view/payment/method-renderer/authorizenet-directpost.js @@ -5,15 +5,16 @@ define( [ 'jquery', - 'Magento_Payment/js/view/payment/iframe' + 'Magento_Payment/js/view/payment/iframe', + 'mage/translate' ], - function ($, Component) { + function ($, Component, $t) { 'use strict'; return Component.extend({ defaults: { template: 'Magento_Authorizenet/payment/authorizenet-directpost', - timeoutMessage: 'Sorry, but something went wrong. Please contact the seller.' + timeoutMessage: $t('Sorry, but something went wrong. Please contact the seller.') }, placeOrderHandler: null, validateHandler: null, diff --git a/app/code/Magento/Backend/composer.json b/app/code/Magento/Backend/composer.json index 7d428636a1f45e23d81879c99baaeef6744518fc..5cfe6955b46bb99f6dd8c76cd58e3ba9bb6f28ad 100644 --- a/app/code/Magento/Backend/composer.json +++ b/app/code/Magento/Backend/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-backend", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-directory": "100.2.*", "magento/module-developer": "100.2.*", diff --git a/app/code/Magento/Backup/composer.json b/app/code/Magento/Backup/composer.json index 21ed6f1780a41348c5bb26556c14fcaa02c08770..ee05d6726db4c3bea7460893242d546a511fe0cc 100644 --- a/app/code/Magento/Backup/composer.json +++ b/app/code/Magento/Backup/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-backup", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-cron": "100.2.*", diff --git a/app/code/Magento/Braintree/Model/Ui/Adminhtml/PayPal/TokenUiComponentProvider.php b/app/code/Magento/Braintree/Model/Ui/Adminhtml/PayPal/TokenUiComponentProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..fd94e18e2cd94b4e4abca81c28d5dcf90c56ff92 --- /dev/null +++ b/app/code/Magento/Braintree/Model/Ui/Adminhtml/PayPal/TokenUiComponentProvider.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Model\Ui\Adminhtml\PayPal; + +use Magento\Braintree\Gateway\Config\PayPal\Config; +use Magento\Braintree\Model\Ui\ConfigProvider; +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider as PayPalConfigProvider; +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Element\Template; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory; +use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; + +/** + * Gets Ui component configuration for Braintree PayPal Vault + */ +class TokenUiComponentProvider implements TokenUiComponentProviderInterface +{ + + /** + * @var TokenUiComponentInterfaceFactory + */ + private $componentFactory; + + /** + * @var UrlInterface + */ + private $urlBuilder; + + /** + * @var Config + */ + private $config; + + /** + * @param TokenUiComponentInterfaceFactory $componentFactory + * @param UrlInterface $urlBuilder + * @param Config $config + */ + public function __construct( + TokenUiComponentInterfaceFactory $componentFactory, + UrlInterface $urlBuilder, + Config $config + ) { + $this->componentFactory = $componentFactory; + $this->urlBuilder = $urlBuilder; + $this->config = $config; + } + + /** + * @inheritdoc + */ + public function getComponentForToken(PaymentTokenInterface $paymentToken) + { + $data = json_decode($paymentToken->getTokenDetails() ?: '{}', true); + $data['icon'] = $this->config->getPayPalIcon(); + $component = $this->componentFactory->create( + [ + 'config' => [ + 'code' => PayPalConfigProvider::PAYPAL_VAULT_CODE, + 'nonceUrl' => $this->getNonceRetrieveUrl(), + TokenUiComponentProviderInterface::COMPONENT_DETAILS => $data, + TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash(), + 'template' => 'Magento_Braintree::form/paypal/vault.phtml' + ], + 'name' => Template::class + ] + ); + + return $component; + } + + /** + * Get url to retrieve payment method nonce + * @return string + */ + private function getNonceRetrieveUrl() + { + return $this->urlBuilder->getUrl(ConfigProvider::CODE . '/payment/getnonce', ['_secure' => true]); + } +} diff --git a/app/code/Magento/Braintree/Model/Ui/Adminhtml/TokenUiComponentProvider.php b/app/code/Magento/Braintree/Model/Ui/Adminhtml/TokenUiComponentProvider.php index 6cfc96ea23d0d158646c4e30be4f63aeb8ca2cd0..420b8365b3ea4f64de1ad6008238c4a706aaa4b6 100644 --- a/app/code/Magento/Braintree/Model/Ui/Adminhtml/TokenUiComponentProvider.php +++ b/app/code/Magento/Braintree/Model/Ui/Adminhtml/TokenUiComponentProvider.php @@ -49,6 +49,7 @@ class TokenUiComponentProvider implements TokenUiComponentProviderInterface $component = $this->componentFactory->create( [ 'config' => [ + 'code' => ConfigProvider::CC_VAULT_CODE, 'nonceUrl' => $this->getNonceRetrieveUrl(), TokenUiComponentProviderInterface::COMPONENT_DETAILS => $data, TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH => $paymentToken->getPublicHash(), diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/PayPal/TokenUiComponentProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/PayPal/TokenUiComponentProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bdc39cbc5b868b52484cd550c833cfa147e68407 --- /dev/null +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/PayPal/TokenUiComponentProviderTest.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Test\Unit\Model\Ui\Adminhtml\PayPal; + +use Magento\Braintree\Gateway\Config\PayPal\Config; +use Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider; +use Magento\Framework\UrlInterface; +use Magento\Vault\Api\Data\PaymentTokenInterface; +use Magento\Vault\Model\Ui\TokenUiComponentInterface; +use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * Contains methods to test PayPal token Ui component provider + */ +class TokenUiComponentProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var TokenUiComponentInterfaceFactory|MockObject + */ + private $componentFactory; + + /** + * @var UrlInterface|MockObject + */ + private $urlBuilder; + + /** + * @var Config|MockObject + */ + private $config; + + /** + * @var TokenUiComponentProvider + */ + private $tokenUiComponentProvider; + + protected function setUp() + { + $this->componentFactory = $this->getMockBuilder(TokenUiComponentInterfaceFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->urlBuilder = $this->getMock(UrlInterface::class); + + $this->config = $this->getMockBuilder(Config::class) + ->disableOriginalConstructor() + ->setMethods(['getPayPalIcon']) + ->getMock(); + + $this->tokenUiComponentProvider = new TokenUiComponentProvider( + $this->componentFactory, + $this->urlBuilder, + $this->config + ); + } + + /** + * @covers \Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider::getComponentForToken + */ + public function testGetComponentForToken() + { + $nonceUrl = 'https://payment/adminhtml/nonce/url'; + $payerEmail = 'john.doe@test.com'; + $icon = [ + 'url' => 'https://payment/adminhtml/icon.png', + 'width' => 48, + 'height' => 32 + ]; + + $expected = [ + 'code' => 'vault', + 'nonceUrl' => $nonceUrl, + 'details' => [ + 'payerEmail' => $payerEmail, + 'icon' => $icon + ], + 'template' => 'vault.phtml' + ]; + + $this->config->expects(static::once()) + ->method('getPayPalIcon') + ->willReturn($icon); + + $paymentToken = $this->getMock(PaymentTokenInterface::class); + $paymentToken->expects(static::once()) + ->method('getTokenDetails') + ->willReturn('{"payerEmail":" ' . $payerEmail . '"}'); + $paymentToken->expects(static::once()) + ->method('getPublicHash') + ->willReturn('cmk32dl21l'); + + $this->urlBuilder->expects(static::once()) + ->method('getUrl') + ->willReturn($nonceUrl); + + $tokenComponent = $this->getMock(TokenUiComponentInterface::class); + $tokenComponent->expects(static::once()) + ->method('getConfig') + ->willReturn($expected); + + $this->componentFactory->expects(static::once()) + ->method('create') + ->willReturn($tokenComponent); + + $component = $this->tokenUiComponentProvider->getComponentForToken($paymentToken); + static::assertEquals($tokenComponent, $component); + static::assertEquals($expected, $component->getConfig()); + } +} diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/TokenUiComponentProviderTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/TokenUiComponentProviderTest.php index d1665c71804cfa0c4ce8d1a6d2db96e09afd932c..f159136cf4c460d43b9191b58b536ff646da8b9c 100644 --- a/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/TokenUiComponentProviderTest.php +++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/TokenUiComponentProviderTest.php @@ -7,10 +7,10 @@ namespace Magento\Braintree\Test\Unit\Model\Ui\Adminhtml; use Magento\Braintree\Model\Ui\Adminhtml\TokenUiComponentProvider; use Magento\Framework\UrlInterface; -use Magento\Framework\View\Element\Template; use Magento\Vault\Api\Data\PaymentTokenInterface; use Magento\Vault\Model\Ui\TokenUiComponentInterface; use Magento\Vault\Model\Ui\TokenUiComponentInterfaceFactory; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Class TokenUiComponentProviderTest @@ -19,12 +19,12 @@ class TokenUiComponentProviderTest extends \PHPUnit_Framework_TestCase { /** - * @var TokenUiComponentInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + * @var TokenUiComponentInterfaceFactory|MockObject */ private $componentFactory; /** - * @var UrlInterface|\PHPUnit_Framework_MockObject_MockObject + * @var UrlInterface|MockObject */ private $urlBuilder; @@ -59,6 +59,7 @@ class TokenUiComponentProviderTest extends \PHPUnit_Framework_TestCase $expirationDate = '12/2015'; $expected = [ + 'code' => 'vault', 'nonceUrl' => $nonceUrl, 'details' => [ 'type' => $type, diff --git a/app/code/Magento/Braintree/composer.json b/app/code/Magento/Braintree/composer.json index 3b77e208837a4a4341baabbae615b7e35b266500..91cacf6add2b891dc4ad759cb6b1e9a6990cb397 100644 --- a/app/code/Magento/Braintree/composer.json +++ b/app/code/Magento/Braintree/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-braintree", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*", "magento/magento-composer-installer": "*", "magento/module-config": "100.2.*", diff --git a/app/code/Magento/Braintree/etc/adminhtml/di.xml b/app/code/Magento/Braintree/etc/adminhtml/di.xml index f252b977f20bd8b8616c7ddd90c08dd7ad0c5435..d154aabbb01b5cdd9db9beba05481c167fbec65d 100644 --- a/app/code/Magento/Braintree/etc/adminhtml/di.xml +++ b/app/code/Magento/Braintree/etc/adminhtml/di.xml @@ -47,6 +47,7 @@ <arguments> <argument name="tokenUiComponentProviders" xsi:type="array"> <item name="braintree" xsi:type="object">Magento\Braintree\Model\Ui\Adminhtml\TokenUiComponentProvider</item> + <item name="braintree_paypal" xsi:type="object">Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider</item> </argument> </arguments> </type> diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml index 095a8419c8529f20beb5f9a2be4499bade1f1301..bf19324ae7a0225b82106f26f291462b9d1e0b12 100644 --- a/app/code/Magento/Braintree/etc/config.xml +++ b/app/code/Magento/Braintree/etc/config.xml @@ -71,7 +71,8 @@ </braintree_cc_vault> <braintree_paypal_vault> <model>BraintreePayPalVaultFacade</model> - <title>Vault Token (Braintree PayPal)</title> + <title>Stored Accounts (Braintree PayPal)</title> + <can_use_internal>1</can_use_internal> </braintree_paypal_vault> </payment> </default> diff --git a/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_index.xml b/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_index.xml index 571c5ededeb993fd0e45d91cc2d1512d531179de..5e4f36e1c1fb4d4916e620cb440829357b1071ea 100644 --- a/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_index.xml +++ b/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_index.xml @@ -18,6 +18,10 @@ <argument name="method" xsi:type="string">braintree_cc_vault</argument> <argument name="template" xsi:type="string">Magento_Vault::form/vault.phtml</argument> </action> + <action method="setMethodFormTemplate"> + <argument name="method" xsi:type="string">braintree_paypal_vault</argument> + <argument name="template" xsi:type="string">Magento_Vault::form/vault.phtml</argument> + </action> </referenceBlock> <referenceBlock name="content"> <block name="braintree_payment_script" diff --git a/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml b/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml index 68e0abc0cd009d9c2a30dff4cb545aa1b85f151c..579b82c61f690ed59bf94423e4aab00348a1546e 100644 --- a/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml +++ b/app/code/Magento/Braintree/view/adminhtml/layout/sales_order_create_load_block_billing_method.xml @@ -18,6 +18,10 @@ <argument name="method" xsi:type="string">braintree_cc_vault</argument> <argument name="template" xsi:type="string">Magento_Vault::form/vault.phtml</argument> </action> + <action method="setMethodFormTemplate"> + <argument name="method" xsi:type="string">braintree_paypal_vault</argument> + <argument name="template" xsi:type="string">Magento_Vault::form/vault.phtml</argument> + </action> </referenceBlock> </body> </page> \ No newline at end of file diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/paypal/vault.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/paypal/vault.phtml new file mode 100644 index 0000000000000000000000000000000000000000..22930bbc656660efb6f5981fd2fb8e137ac591df --- /dev/null +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/paypal/vault.phtml @@ -0,0 +1,30 @@ +<?php +use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +// @codingStandardsIgnoreFile + +/** @var \Magento\Framework\View\Element\Template $block */ +$details = $block->getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS); +$icon = $details['icon']; +$id = $block->escapeHtml($block->getData('id')); +?> +<div data-mage-init='{ + "Magento_Braintree/js/vault": { + "container": "payment_<?php /* @noEscape */ echo $id; ?>", + "publicHash": "<?php echo $block->escapeHtml($block->getData(TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH)); ?>", + "code": "<?php echo $block->escapeHtml($block->getData('code')); ?>", + "nonceUrl": "<?php echo $block->escapeUrl($block->getData('nonceUrl')); ?>" + } + }' id="payment_<?php /* @noEscape */ echo $id;?>" class="admin__field"> + <div class="admin__field-control control"> + <input type="radio" id="token_switcher_<?php /* @noEscape */ echo $id; ?>" name="payment[token_switcher]"/> + <img src="<?php echo $block->escapeUrl($icon['url']); ?>" + width="<?php echo $block->escapeHtml($icon['width']); ?>" + height="<?php echo $block->escapeHtml($icon['height']); ?>" + class="payment-icon" > + <span><?php echo $block->escapeHtml($details['payerEmail']); ?></span> + </div> +</div> diff --git a/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml b/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml index 38114618847259c143ec2794d13ccaf5a5ff1349..001422d4bf911a40094d2174f9bb346866175c59 100644 --- a/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml +++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/vault.phtml @@ -7,7 +7,7 @@ use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; // @codingStandardsIgnoreFile /** @var \Magento\Framework\View\Element\Template $block */ -$details = $block->getData('details'); +$details = $block->getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS); $icon = $block->getData('icons')[$details['type']]; $id = $block->escapeHtml($block->getData('id')); ?> @@ -15,6 +15,7 @@ $id = $block->escapeHtml($block->getData('id')); "Magento_Braintree/js/vault": { "container": "payment_<?php /* @noEscape */ echo $id; ?>", "publicHash": "<?php echo $block->escapeHtml($block->getData(TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH)); ?>", + "code": "<?php echo $block->escapeHtml($block->getData('code')); ?>", "nonceUrl": "<?php echo $block->escapeUrl($block->getData('nonceUrl')); ?>" } }' id="payment_<?php /* @noEscape */ echo $id;?>" class="admin__field"> diff --git a/app/code/Magento/Braintree/view/adminhtml/web/js/vault.js b/app/code/Magento/Braintree/view/adminhtml/web/js/vault.js index fcff173e7fcd44f714a6ec9f4282f838b04654aa..ea832acb537e0051e34f976c050c9b8bd7d289b7 100644 --- a/app/code/Magento/Braintree/view/adminhtml/web/js/vault.js +++ b/app/code/Magento/Braintree/view/adminhtml/web/js/vault.js @@ -14,7 +14,8 @@ define([ return Class.extend({ defaults: { $selector: null, - selector: 'edit_form' + selector: 'edit_form', + $container: null }, /** @@ -25,17 +26,18 @@ define([ var self = this; self.$selector = $('#' + self.selector); + self.$container = $('#' + self.container); self.$selector.on( 'setVaultNotActive', function () { - self.$selector.off('submitOrder.braintree_vault'); + self.$selector.off('submitOrder.' + self.getCode()); } ); - this._super(); + self._super(); - this.initEventHandlers(); + self.initEventHandlers(); - return this; + return self; }, /** @@ -43,14 +45,14 @@ define([ * @returns {String} */ getCode: function () { - return 'braintree'; + return this.code; }, /** * Init event handlers */ initEventHandlers: function () { - $('#' + this.container).find('[name="payment[token_switcher]"]') + $(this.$container).find('[name="payment[token_switcher]"]') .on('click', this.selectPaymentMethod.bind(this)); }, @@ -66,7 +68,7 @@ define([ * Enable form event listeners */ enableEventListeners: function () { - this.$selector.on('submitOrder.braintree_vault', this.submitOrder.bind(this)); + this.$selector.on('submitOrder.' + this.getCode(), this.submitOrder.bind(this)); }, /** @@ -129,7 +131,7 @@ define([ this.createPublicHashSelector(); this.$selector.find('[name="payment[public_hash]"]').val(this.publicHash); - this.$selector.find('#braintree_nonce').val(nonce); + this.$container.find('#' + this.getNonceSelectorName()).val(nonce); }, /** @@ -138,16 +140,16 @@ define([ createPublicHashSelector: function () { var $input; - if (this.$selector.find('#braintree_nonce').size() === 0) { + if (this.$container.find('#' + this.getNonceSelectorName()).size() === 0) { $input = $('<input>').attr( { type: 'hidden', - id: 'braintree_nonce', + id: this.getNonceSelectorName(), name: 'payment[payment_method_nonce]' } ); - $input.appendTo(this.$selector); + $input.appendTo(this.$container); $input.prop('disabled', false); } }, @@ -160,6 +162,14 @@ define([ alert({ content: message }); + }, + + /** + * Get selector name for nonce input + * @returns {String} + */ + getNonceSelectorName: function () { + return 'nonce_' + this.getCode(); } }); }); 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 184260c39e954d1c5a1fd6d45b34f09fcab9daa7..075a1fdaf1fc118f42832fa438762715420a585a 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 @@ -98,7 +98,6 @@ define([ quote.totals.subscribe(function () { if (self.grandTotalAmount !== quote.totals()['base_grand_total']) { self.grandTotalAmount = quote.totals()['base_grand_total']; - self.reInitPayPal(); } }); 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 b349d7da577a4056c86e04e7293cc53a7406a4d8..30b0d6f2ac72cff0062ed5dec33dd8498c696200 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 @@ -78,9 +78,14 @@ class Bundle extends \Magento\Catalog\Block\Product\View\AbstractView } /** + * Returns the bundle product options + * Will return cached options data if the product options are already initialized + * In a case when $stripSelection parameter is true will reload stored bundle selections collection from DB + * + * @param bool $stripSelection * @return array */ - public function getOptions() + public function getOptions($stripSelection = false) { if (!$this->options) { $product = $this->getProduct(); @@ -96,7 +101,7 @@ class Bundle extends \Magento\Catalog\Block\Product\View\AbstractView $this->options = $optionCollection->appendSelections( $selectionCollection, - false, + $stripSelection, $this->catalogProduct->getSkipSaleableCheck() ); } diff --git a/app/code/Magento/Bundle/Setup/InstallSchema.php b/app/code/Magento/Bundle/Setup/InstallSchema.php index 6f98cd386327a182243b4348fa6dc0d02f56cd4e..5c7d5a47da616e801a3dc0e2fc05ab3d0eb69c04 100644 --- a/app/code/Magento/Bundle/Setup/InstallSchema.php +++ b/app/code/Magento/Bundle/Setup/InstallSchema.php @@ -24,7 +24,9 @@ class InstallSchema implements InstallSchemaInterface $installer = $setup; $installer->startSetup(); - + $customerGroupTable = $setup->getConnection()->describeTable($setup->getTable('customer_group')); + $customerGroupIdType = $customerGroupTable['customer_group_id']['DATA_TYPE'] == 'int' + ? \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER : $customerGroupTable['customer_group_id']['DATA_TYPE']; /** * Create table 'catalog_product_bundle_option' */ @@ -340,7 +342,7 @@ class InstallSchema implements InstallSchemaInterface ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Customer Group Id' diff --git a/app/code/Magento/Bundle/Setup/UpgradeSchema.php b/app/code/Magento/Bundle/Setup/UpgradeSchema.php old mode 100644 new mode 100755 index e48ad97922dc99540f8e4d9b0fb8450d2d023365..ced66a03a3a865cc14e1050d2df94df5c1762ad0 --- a/app/code/Magento/Bundle/Setup/UpgradeSchema.php +++ b/app/code/Magento/Bundle/Setup/UpgradeSchema.php @@ -44,6 +44,24 @@ class UpgradeSchema implements UpgradeSchemaInterface } } + if (version_compare($context->getVersion(), '2.0.3', '<')) { + $tables = [ + 'catalog_product_index_price_bundle_idx', + 'catalog_product_index_price_bundle_opt_idx', + 'catalog_product_index_price_bundle_opt_tmp', + 'catalog_product_index_price_bundle_sel_idx', + 'catalog_product_index_price_bundle_sel_tmp', + 'catalog_product_index_price_bundle_tmp', + ]; + foreach ($tables as $table) { + $setup->getConnection()->modifyColumn( + $setup->getTable($table), + 'customer_group_id', + ['type' => 'integer', 'nullable' => false] + ); + } + } + $setup->endSetup(); } } diff --git a/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php b/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php index 362394ccd8bd1ddb245f17d27047fddb094f107d..f11fc30f5b28f90ec7f1a2c13a9b40b714f7cebe 100644 --- a/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php +++ b/app/code/Magento/Bundle/Test/Unit/Block/Catalog/Product/View/Type/BundleTest.php @@ -19,32 +19,34 @@ class BundleTest extends \PHPUnit_Framework_TestCase /** @var \Magento\Bundle\Model\Product\PriceFactory|\PHPUnit_Framework_MockObject_MockObject */ private $bundleProductPriceFactory; - /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager - */ - protected $_objectHelper; + /** @var \Magento\Framework\Json\Encoder|\PHPUnit_Framework_MockObject_MockObject */ + private $jsonEncoder; + + /** @var \Magento\Catalog\Helper\Product|\PHPUnit_Framework_MockObject_MockObject */ + private $catalogProduct; /** - * @var \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle + * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $_bundleBlock; + private $eventManager; /** @var \Magento\Catalog\Model\Product|\PHPUnit_Framework_MockObject_MockObject */ private $product; + /** + * @var \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle + */ + private $bundleBlock; + protected function setUp() { $objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->bundleProductPriceFactory = $this->getMockBuilder(\Magento\Bundle\Model\Product\PriceFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->_bundleBlock = $objectHelper->getObject( - \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle::class, - [ - 'productPrice' => $this->bundleProductPriceFactory - ] - ); + $this->product = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) ->disableOriginalConstructor() ->setMethods( @@ -57,45 +59,78 @@ class BundleTest extends \PHPUnit_Framework_TestCase 'getPreconfiguredValues' ] )->getMock(); + $registry = $this->getMockBuilder(\Magento\Framework\Registry::class) + ->disableOriginalConstructor() + ->setMethods(['registry']) + ->getMock(); + $registry->expects($this->any()) + ->method('registry') + ->willReturn($this->product); + $this->eventManager = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->jsonEncoder = $this->getMockBuilder(\Magento\Framework\Json\Encoder::class) + ->disableOriginalConstructor() + ->getMock(); + $this->catalogProduct = $this->getMockBuilder(\Magento\Catalog\Helper\Product::class) + ->disableOriginalConstructor() + ->getMock(); + /** @var $bundleBlock BundleBlock */ + $this->bundleBlock = $objectHelper->getObject( + \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle::class, + [ + 'registry' => $registry, + 'eventManager' => $this->eventManager, + 'jsonEncoder' => $this->jsonEncoder, + 'productPrice' => $this->bundleProductPriceFactory, + 'catalogProduct' => $this->catalogProduct + ] + ); } public function testGetOptionHtmlNoRenderer() { - $option = $this->getMock(\Magento\Bundle\Model\Option::class, ['getType', '__wakeup'], [], '', false); - $option->expects($this->exactly(2))->method('getType')->will($this->returnValue('checkbox')); + $option = $this->getMockBuilder(\Magento\Bundle\Model\Option::class) + ->setMethods(['getType']) + ->disableOriginalConstructor() + ->getMock(); + $option->expects($this->any())->method('getType')->willReturn('checkbox'); + + $layout = $this->getMockBuilder(\Magento\Framework\View\Layout::class) + ->setMethods(['getChildName', 'getBlock']) + ->disableOriginalConstructor() + ->getMock(); + $layout->expects($this->any())->method('getChildName')->willReturn(false); + $this->bundleBlock->setLayout($layout); $this->assertEquals( 'There is no defined renderer for "checkbox" option type.', - $this->_bundleBlock->getOptionHtml($option) + $this->bundleBlock->getOptionHtml($option) ); } public function testGetOptionHtml() { - $option = $this->getMock(\Magento\Bundle\Model\Option::class, ['getType', '__wakeup'], [], '', false); - $option->expects($this->exactly(1))->method('getType')->will($this->returnValue('checkbox')); - - $optionBlock = $this->getMock( - \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Checkbox::class, - ['setOption', 'toHtml'], - [], - '', - false - ); - $optionBlock->expects($this->any())->method('setOption')->will($this->returnValue($optionBlock)); - $optionBlock->expects($this->any())->method('toHtml')->will($this->returnValue('option html')); - $layout = $this->getMock( - \Magento\Framework\View\Layout::class, - ['getChildName', 'getBlock'], - [], - '', - false - ); - $layout->expects($this->any())->method('getChildName')->will($this->returnValue('name')); - $layout->expects($this->any())->method('getBlock')->will($this->returnValue($optionBlock)); - $this->_bundleBlock->setLayout($layout); + $option = $this->getMockBuilder(\Magento\Bundle\Model\Option::class) + ->setMethods(['getType']) + ->disableOriginalConstructor() + ->getMock(); + $option->expects($this->once())->method('getType')->willReturn('checkbox'); + + $optionBlock = $this->getMockBuilder( + \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle\Option\Checkbox::class + )->setMethods(['setOption', 'toHtml'])->disableOriginalConstructor()->getMock(); + $optionBlock->expects($this->any())->method('setOption')->willReturnSelf(); + $optionBlock->expects($this->any())->method('toHtml')->willReturn('option html'); + $layout = $this->getMockBuilder(\Magento\Framework\View\Layout::class) + ->setMethods(['getChildName', 'getBlock']) + ->disableOriginalConstructor() + ->getMock(); + $layout->expects($this->any())->method('getChildName')->willReturn('name'); + $layout->expects($this->any())->method('getBlock')->willReturn($optionBlock); + $this->bundleBlock->setLayout($layout); - $this->assertEquals('option html', $this->_bundleBlock->getOptionHtml($option)); + $this->assertEquals('option html', $this->bundleBlock->getOptionHtml($option)); } public function testGetJsonConfigFixedPriceBundleNoOption() @@ -127,12 +162,12 @@ class BundleTest extends \PHPUnit_Framework_TestCase ]; $priceInfo = $this->getPriceInfoMock($prices); - $this->_bundleBlock = $this->setupBundleBlock( + $this->updateBundleBlock( $options, $priceInfo, \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED ); - $jsonConfig = $this->_bundleBlock->getJsonConfig(); + $jsonConfig = $this->bundleBlock->getJsonConfig(); $this->assertEquals(110, $jsonConfig['prices']['oldPrice']['amount']); $this->assertEquals(100, $jsonConfig['prices']['basePrice']['amount']); $this->assertEquals(100, $jsonConfig['prices']['finalPrice']['amount']); @@ -162,14 +197,14 @@ class BundleTest extends \PHPUnit_Framework_TestCase $bundleProductPrice->expects($this->at(0)) ->method('getLowestPrice') ->with($this->product, $baseAmount) - ->will($this->returnValue(999)); + ->willReturn(999); $bundleProductPrice->expects($this->at(1)) ->method('getLowestPrice') ->with($this->product, $basePriceValue) - ->will($this->returnValue(888)); + ->willReturn(888); $this->bundleProductPriceFactory->expects($this->once()) ->method('create') - ->will($this->returnValue($bundleProductPrice)); + ->willReturn($bundleProductPrice); $options = [ $this->createOption(1, 'Title `1', $selections), @@ -207,7 +242,7 @@ class BundleTest extends \PHPUnit_Framework_TestCase $this->product->expects($this->once()) ->method('hasPreconfiguredValues') - ->will($this->returnValue(true)); + ->willReturn(true); $preconfiguredValues = new \Magento\Framework\DataObject( [ 'bundle_option' => [ @@ -217,14 +252,14 @@ class BundleTest extends \PHPUnit_Framework_TestCase ); $this->product->expects($this->once()) ->method('getPreconfiguredValues') - ->will($this->returnValue($preconfiguredValues)); + ->willReturn($preconfiguredValues); - $this->_bundleBlock = $this->setupBundleBlock( + $this->updateBundleBlock( $options, $priceInfo, \Magento\Bundle\Model\Product\Price::PRICE_TYPE_FIXED ); - $jsonConfig = $this->_bundleBlock->getJsonConfig(); + $jsonConfig = $this->bundleBlock->getJsonConfig(); $this->assertEquals(110, $jsonConfig['prices']['oldPrice']['amount']); $this->assertEquals(100, $jsonConfig['prices']['basePrice']['amount']); $this->assertEquals(100, $jsonConfig['prices']['finalPrice']['amount']); @@ -236,86 +271,38 @@ class BundleTest extends \PHPUnit_Framework_TestCase * @param string $priceType * @return BundleBlock */ - private function setupBundleBlock($options, $priceInfo, $priceType) + private function updateBundleBlock($options, $priceInfo, $priceType) { - $objectHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - - $eventManager = $this->getMockBuilder(\Magento\Framework\Event\Manager::class) - ->disableOriginalConstructor() - ->getMock(); - $eventManager->expects($this->any())->method('dispatch')->will($this->returnValue(true)); - + $this->eventManager->expects($this->any())->method('dispatch')->willReturn(true); $optionCollection = $this->getMockBuilder(\Magento\Bundle\Model\ResourceModel\Option\Collection::class) ->disableOriginalConstructor() ->getMock(); $optionCollection->expects($this->any()) ->method('appendSelections') - ->will($this->returnValue($options)); + ->willReturn($options); $typeInstance = $this->getMockBuilder(\Magento\Bundle\Model\Product\Type::class) ->disableOriginalConstructor() ->getMock(); $typeInstance->expects($this->any()) ->method('getOptionsCollection') - ->will($this->returnValue($optionCollection)); + ->willReturn($optionCollection); $typeInstance->expects($this->any()) ->method('getStoreFilter') - ->will($this->returnValue(true)); + ->willReturn(true); $this->product->expects($this->any()) ->method('getTypeInstance') - ->will($this->returnValue($typeInstance)); + ->willReturn($typeInstance); $this->product->expects($this->any()) ->method('getPriceInfo') - ->will($this->returnValue($priceInfo)); + ->willReturn($priceInfo); $this->product->expects($this->any()) ->method('getPriceType') - ->will($this->returnValue($priceType)); - - $registry = $this->getMockBuilder(\Magento\Framework\Registry::class) - ->disableOriginalConstructor() - ->setMethods(['registry']) - ->getMock(); - $registry->expects($this->once()) - ->method('registry') - ->will($this->returnValue($this->product)); - - $taxHelperMock = $this->getMockBuilder(\Magento\Tax\Helper\Data::class) - ->disableOriginalConstructor() - ->getMock(); - - $context = $this->getMockBuilder(\Magento\Catalog\Block\Product\Context::class) - ->disableOriginalConstructor() - ->getMock(); - $context->expects($this->any()) - ->method('getRegistry') - ->will($this->returnValue($registry)); - $context->expects($this->any()) - ->method('getTaxData') - ->will($this->returnValue($taxHelperMock)); - $context->expects($this->any()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $jsonEncoderMock = $this->getMockBuilder(\Magento\Framework\Json\Encoder::class) - ->disableOriginalConstructor() - ->getMock(); - $jsonEncoderMock->expects($this->any()) + ->willReturn($priceType); + $this->jsonEncoder->expects($this->any()) ->method('encode') ->will($this->returnArgument(0)); - - /** @var $bundleBlock BundleBlock */ - $bundleBlock = $objectHelper->getObject( - \Magento\Bundle\Block\Catalog\Product\View\Type\Bundle::class, - [ - 'context' => $context, - 'jsonEncoder' => $jsonEncoderMock, - 'productPrice' => $this->bundleProductPriceFactory - ] - ); - - return $bundleBlock; } private function getPriceInfoMock($price) @@ -331,13 +318,13 @@ class BundleTest extends \PHPUnit_Framework_TestCase $priceInfoMock->expects($this->at($counter)) ->method('getPrice') ->with($priceType) - ->will($this->returnValue($priceValue)); + ->willReturn($priceValue); $counter++; } } else { $priceInfoMock->expects($this->any()) ->method('getPrice') - ->will($this->returnValue($price)); + ->willReturn($price); } return $priceInfoMock; } @@ -355,7 +342,7 @@ class BundleTest extends \PHPUnit_Framework_TestCase foreach ($prices as $methodName => $amount) { $priceMock->expects($this->any()) ->method($methodName) - ->will($this->returnValue($amount)); + ->willReturn($amount); } return $priceMock; @@ -373,8 +360,8 @@ class BundleTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods(['getValue', 'getBaseAmount', 'getOptionSelectionAmount']) ->getMockForAbstractClass(); - $amountPrice->expects($this->any())->method('getValue')->will($this->returnValue($value)); - $amountPrice->expects($this->any())->method('getBaseAmount')->will($this->returnValue($baseAmount)); + $amountPrice->expects($this->any())->method('getValue')->willReturn($value); + $amountPrice->expects($this->any())->method('getBaseAmount')->willReturn($baseAmount); foreach ($selectionAmounts as $selectionAmount) { $amountPrice->expects($this->any()) ->method('getOptionSelectionAmount') @@ -414,7 +401,6 @@ class BundleTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->setMethods( [ - '__wakeup', 'getId', 'getTitle', 'getSelections', @@ -423,12 +409,12 @@ class BundleTest extends \PHPUnit_Framework_TestCase 'getIsDefault', ] ) - ->getMock(); - $option->expects($this->any())->method('getId')->will($this->returnValue($id)); - $option->expects($this->any())->method('getTitle')->will($this->returnValue($title)); - $option->expects($this->any())->method('getSelections')->will($this->returnValue($selections)); - $option->expects($this->any())->method('getType')->will($this->returnValue($type)); - $option->expects($this->any())->method('getRequired')->will($this->returnValue($isRequired)); + ->getMockForAbstractClass(); + $option->expects($this->any())->method('getId')->willReturn($id); + $option->expects($this->any())->method('getTitle')->willReturn($title); + $option->expects($this->any())->method('getSelections')->willReturn($selections); + $option->expects($this->any())->method('getType')->willReturn($type); + $option->expects($this->any())->method('getRequired')->willReturn($isRequired); return $option; } @@ -453,42 +439,72 @@ class BundleTest extends \PHPUnit_Framework_TestCase ) { $selection = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) ->disableOriginalConstructor() - ->setMethods( - [ - 'getSelectionId', - 'getSelectionQty', - 'getPriceInfo', - 'getSelectionCanChangeQty', - 'getName', - 'getIsDefault', - 'isSalable', - ] - )->getMock(); + ->getMock(); $tierPrice = $this->getMockBuilder(\Magento\Bundle\Pricing\Price\TierPrice::class) ->disableOriginalConstructor() ->setMethods(['getTierPriceList']) ->getMock(); - $tierPrice->expects($this->any()) - ->method('getTierPriceList') - ->will($this->returnValue($tierPriceList)); + $tierPrice->expects($this->any())->method('getTierPriceList')->willReturn($tierPriceList); $priceInfo = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo\Base::class) ->disableOriginalConstructor() ->setMethods(['getPrice']) ->getMock(); - $priceInfo->expects($this->any()) - ->method('getPrice') - ->will($this->returnValue($tierPrice)); - - $selection->expects($this->any())->method('getSelectionId')->will($this->returnValue($id)); - $selection->expects($this->any())->method('getName')->will($this->returnValue($name)); - $selection->expects($this->any())->method('getSelectionQty')->will($this->returnValue($qty)); - $selection->expects($this->any())->method('getPriceInfo')->will($this->returnValue($priceInfo)); - $selection->expects($this->any())->method('getSelectionCanChangeQty')->will( - $this->returnValue($isCanChangeQty) - ); - $selection->expects($this->any())->method('getIsDefault')->will($this->returnValue($isDefault)); - $selection->expects($this->any())->method('isSalable')->will($this->returnValue($isSalable)); + $priceInfo->expects($this->any())->method('getPrice')->willReturn($tierPrice); + $selection->expects($this->any())->method('getSelectionId')->willReturn($id); + $selection->expects($this->any())->method('getName')->willReturn($name); + $selection->expects($this->any())->method('getSelectionQty')->willReturn($qty); + $selection->expects($this->any())->method('getPriceInfo')->willReturn($priceInfo); + $selection->expects($this->any())->method('getSelectionCanChangeQty')->willReturn($isCanChangeQty); + $selection->expects($this->any())->method('getIsDefault')->willReturn($isDefault); + $selection->expects($this->any())->method('isSalable')->willReturn($isSalable); return $selection; } + + /** + * @dataProvider getOptionsDataProvider + * @param bool $stripSelection + */ + public function testGetOptions($stripSelection) + { + $newOptions = ['option_1', 'option_2']; + + $optionCollection = $this->getMockBuilder(\Magento\Bundle\Model\ResourceModel\Option\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $selectionConnection = $this->getMockBuilder(\Magento\Bundle\Model\ResourceModel\Selection\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $typeInstance = $this->getMockBuilder(\Magento\Bundle\Model\Product\Type::class) + ->disableOriginalConstructor() + ->getMock(); + + $optionCollection->expects($this->any())->method('appendSelections') + ->with($selectionConnection, $stripSelection, true) + ->willReturn($newOptions); + $typeInstance->expects($this->any())->method('setStoreFilter')->with(0, $this->product) + ->willReturn($optionCollection); + $typeInstance->expects($this->any())->method('getStoreFilter')->willReturn(true); + $typeInstance->expects($this->any())->method('getOptionsCollection')->willReturn($optionCollection); + $typeInstance->expects($this->any())->method('getOptionsIds')->willReturn([1,2]); + $typeInstance->expects($this->once())->method('getSelectionsCollection')->with([1,2], $this->product) + ->willReturn($selectionConnection); + $this->product->expects($this->any()) + ->method('getTypeInstance')->willReturn($typeInstance); + $this->product->expects($this->any())->method('getStoreId') ->willReturn(0); + $this->catalogProduct->expects($this->once())->method('getSkipSaleableCheck')->willReturn(true); + + $this->assertEquals($newOptions, $this->bundleBlock->getOptions($stripSelection)); + } + + /** + * @return array + */ + public function getOptionsDataProvider() + { + return [ + [true], + [false] + ]; + } } diff --git a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php index 50a3caaf68b3bd39ce1458e4883b4c1dd35c0525..538c80d9b1cf27af4ba14c911b0d0a6913c403c7 100644 --- a/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php +++ b/app/code/Magento/Bundle/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php @@ -268,15 +268,12 @@ class BundlePanel extends AbstractModifier 'arguments' => [ 'data' => [ 'config' => [ - 'componentType' => 'dynamicRows', + 'componentType' => Container::NAME, + 'component' => 'Magento_Bundle/js/components/bundle-dynamic-rows', 'template' => 'ui/dynamic-rows/templates/collapsible', - 'label' => '', 'additionalClasses' => 'admin__field-wide', - 'collapsibleHeader' => true, - 'columnsHeader' => false, - 'deleteProperty' => false, - 'addButton' => false, 'dataScope' => 'data.bundle_options', + 'bundleSelectionsName' => 'product_bundle_container.bundle_selections' ], ], ], @@ -318,14 +315,11 @@ class BundlePanel extends AbstractModifier 'arguments' => [ 'data' => [ 'config' => [ - 'componentType' => DynamicRows::NAME, - 'label' => '', + 'componentType' => Container::NAME, + 'component' => 'Magento_Bundle/js/components/bundle-dynamic-rows-grid', 'sortOrder' => 50, 'additionalClasses' => 'admin__field-wide', - 'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows-grid', 'template' => 'ui/dynamic-rows/templates/default', - 'columnsHeader' => false, - 'columnsHeaderAfterRender' => true, 'provider' => 'product_form.product_form_data_source', 'dataProvider' => '${ $.dataScope }' . '.bundle_button_proxy', 'identificationDRProperty' => 'product_id', @@ -343,8 +337,7 @@ class BundlePanel extends AbstractModifier 'selection_qty' => '', ], 'links' => ['insertData' => '${ $.provider }:${ $.dataProvider }'], - 'source' => 'product', - 'addButton' => false, + 'source' => 'product' ], ], ], @@ -561,7 +554,7 @@ class BundlePanel extends AbstractModifier 'componentType' => Container::NAME, 'isTemplate' => true, 'component' => 'Magento_Ui/js/dynamic-rows/record', - 'is_collection' => true, + 'is_collection' => true ], ], ], diff --git a/app/code/Magento/Bundle/composer.json b/app/code/Magento/Bundle/composer.json index ab587044476db47c0a824cfdb130988f22591971..24756bdf950a7a42431e31cbc08090ca0141eb27 100644 --- a/app/code/Magento/Bundle/composer.json +++ b/app/code/Magento/Bundle/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-bundle", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-tax": "100.2.*", diff --git a/app/code/Magento/Bundle/etc/module.xml b/app/code/Magento/Bundle/etc/module.xml index 982a33d00bc6b9dd94f57a5b3b2e4dbd41d719b9..34a88d4447cc3648a94f52c473831959111f5aed 100644 --- a/app/code/Magento/Bundle/etc/module.xml +++ b/app/code/Magento/Bundle/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_Bundle" setup_version="2.0.2"> + <module name="Magento_Bundle" setup_version="2.0.3"> <sequence> <module name="Magento_Catalog"/> </sequence> diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/bundle.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/bundle.phtml index f0cb656843bcac4075a0df0659d97c8c349d2c00..1272e13e42526f1849e0e61695338ef107172428 100644 --- a/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/bundle.phtml +++ b/app/code/Magento/Bundle/view/adminhtml/templates/product/composite/fieldset/options/bundle.phtml @@ -9,7 +9,7 @@ ?> <?php /* @var $block \Magento\Bundle\Block\Adminhtml\Catalog\Product\Composite\Fieldset\Bundle */ ?> -<?php $options = $block->decorateArray($block->getOptions()); ?> +<?php $options = $block->decorateArray($block->getOptions(true)); ?> <?php if (count($options)): ?> <fieldset id="catalog_product_composite_configure_fields_bundle" class="fieldset admin__fieldset composite-bundle<?php echo $block->getIsLastFieldset() ? ' last-fieldset' : '' ?>"> diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js new file mode 100644 index 0000000000000000000000000000000000000000..e9a924e1cffe696688fed502bc715347c8afb8d2 --- /dev/null +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows-grid.js @@ -0,0 +1,59 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'Magento_Ui/js/dynamic-rows/dynamic-rows-grid' +], function (_, dynamicRowsGrid) { + 'use strict'; + + return dynamicRowsGrid.extend({ + defaults: { + label: '', + columnsHeader: false, + columnsHeaderAfterRender: true, + addButton: false + }, + + /** + * Initialize elements from grid + * + * @param {Array} data + * + * @returns {Object} Chainable. + */ + initElements: function (data) { + var newData = this.getNewData(data), + recordIndex; + + this.parsePagesData(data); + + if (newData.length) { + if (this.insertData().length) { + recordIndex = data.length - newData.length - 1; + + _.each(newData, function (newRecord) { + this.processingAddChild(newRecord, ++recordIndex, newRecord[this.identificationProperty]); + }, this); + } + } + + return this; + }, + + /** + * Mapping value from grid + * + * @param {Array} data + */ + mappingValue: function (data) { + if (_.isEmpty(data)) { + return; + } + + this._super(); + } + }); +}); diff --git a/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js new file mode 100644 index 0000000000000000000000000000000000000000..b36d8003a399fb4af88d3e3cfab34559e2e80bea --- /dev/null +++ b/app/code/Magento/Bundle/view/adminhtml/web/js/components/bundle-dynamic-rows.js @@ -0,0 +1,98 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'underscore', + 'mageUtils', + 'uiRegistry', + 'Magento_Ui/js/dynamic-rows/dynamic-rows' +], function (_, utils, registry, dynamicRows) { + 'use strict'; + + return dynamicRows.extend({ + defaults: { + label: '', + collapsibleHeader: true, + columnsHeader: false, + deleteProperty: false, + addButton: false + }, + + /** + * Set new data to dataSource, + * delete element + * + * @param {Array} data - record data + */ + _updateData: function (data) { + var elems = _.clone(this.elems()), + path, + dataArr, + optionBaseData; + + dataArr = this.recordData.splice(this.startIndex, this.recordData().length - this.startIndex); + dataArr.splice(0, this.pageSize); + elems = _.sortBy(this.elems(), function (elem) { + return ~~elem.index; + }); + + data.concat(dataArr).forEach(function (rec, idx) { + if (elems[idx]) { + elems[idx].recordId = rec[this.identificationProperty]; + } + + if (!rec.position) { + rec.position = this.maxPosition; + this.setMaxPosition(); + } + + path = this.dataScope + '.' + this.index + '.' + (this.startIndex + idx); + optionBaseData = _.pick(rec, function (value) { + return !_.isObject(value); + }); + this.source.set(path, optionBaseData); + this.source.set(path + '.bundle_button_proxy', []); + this.source.set(path + '.bundle_selections', []); + this.removeBundleItemsFromOption(idx); + _.each(rec['bundle_selections'], function (obj, index) { + this.source.set(path + '.bundle_button_proxy' + '.' + index, rec['bundle_button_proxy'][index]); + this.source.set(path + '.bundle_selections' + '.' + index, obj); + }, this); + }, this); + + this.elems(elems); + }, + + /** + * Removes nested dynamic-rows-grid rendered records from option + * + * @param {Number|String} index - element index + */ + removeBundleItemsFromOption: function (index) { + var bundleSelections = registry.get(this.name + '.' + index + '.' + this.bundleSelectionsName), + bundleSelectionsLength = (bundleSelections.elems() || []).length, + i; + + if (bundleSelectionsLength) { + for (i = 0; i < bundleSelectionsLength; i++) { + bundleSelections.elems()[0].destroy(); + } + } + }, + + /** + * {@inheritdoc} + */ + processingAddChild: function (ctx, index, prop) { + var recordIds = _.map(this.recordData(), function (rec) { + return parseInt(rec['record_id'], 10); + }), + maxRecordId = _.max(recordIds); + + prop = maxRecordId > -1 ? maxRecordId + 1 : prop; + this._super(ctx, index, prop); + } + }); +}); diff --git a/app/code/Magento/BundleImportExport/composer.json b/app/code/Magento/BundleImportExport/composer.json index 3d7900d98287c1eba32a5badd2d5b2fdc1857f25..2f5e3afdbff86ded9c88161ebea832c29b37f229 100644 --- a/app/code/Magento/BundleImportExport/composer.json +++ b/app/code/Magento/BundleImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-bundle-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-import-export": "100.2.*", "magento/module-catalog-import-export": "100.2.*", diff --git a/app/code/Magento/CacheInvalidate/composer.json b/app/code/Magento/CacheInvalidate/composer.json index cafccadb41ad869ff87ee8eb154c747f569a4c79..c886e069b66e37d3b0c7a12af377a250cfc75192 100644 --- a/app/code/Magento/CacheInvalidate/composer.json +++ b/app/code/Magento/CacheInvalidate/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-cache-invalidate", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-page-cache": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/app/code/Magento/Captcha/composer.json b/app/code/Magento/Captcha/composer.json index 9d0ce3db92aa402a7c247fed21185953c4707342..45ecd32464e9cc3f647264420961d6848b0445e9 100644 --- a/app/code/Magento/Captcha/composer.json +++ b/app/code/Magento/Captcha/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-captcha", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-customer": "100.2.*", "magento/module-checkout": "100.2.*", diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg/Content.php b/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg/Content.php index d5488ecc5d085ee4a5d4b535ccf24040dbaee2a6..ac8cb91222fba47c7c686c10b45d6b81e87db2eb 100644 --- a/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg/Content.php +++ b/app/code/Magento/Catalog/Block/Adminhtml/Helper/Form/Wysiwyg/Content.php @@ -14,6 +14,12 @@ namespace Magento\Catalog\Block\Adminhtml\Helper\Form\Wysiwyg; use Magento\Backend\Block\Widget\Form; use Magento\Backend\Block\Widget\Form\Generic; +/** + * Class Content + * + * @deprecated + * @see \Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Eav + */ class Content extends Generic { /** 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 285f4f1e3ffade4875266c634a241768b36215e9..25bd24ef70b9492d2da5b32e2a4146e3b1a0ab07 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Image/Upload.php @@ -50,8 +50,10 @@ class Upload extends \Magento\Backend\App\Action */ public function execute() { + $imageId = $this->_request->getParam('param_name', 'image'); + try { - $result = $this->imageUploader->saveFileToTmpDir('image'); + $result = $this->imageUploader->saveFileToTmpDir($imageId); $result['cookie'] = [ 'name' => $this->_getSession()->getName(), diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php index 55ae96faeb7e6424f51ec3d5a22054844ea5a812..fa29d46c615939533111d3f62c540f848f06710b 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php @@ -6,6 +6,7 @@ namespace Magento\Catalog\Controller\Adminhtml\Category; use Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Api\Data\CategoryAttributeInterface; /** * Class Save @@ -48,6 +49,11 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Category */ private $storeManager; + /** + * @var \Magento\Eav\Model\Config + */ + private $eavConfig; + /** * Constructor * @@ -56,31 +62,35 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Category * @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory * @param \Magento\Framework\View\LayoutFactory $layoutFactory * @param StoreManagerInterface $storeManager + * @param \Magento\Eav\Model\Config $eavConfig */ public function __construct( \Magento\Backend\App\Action\Context $context, \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory, \Magento\Framework\View\LayoutFactory $layoutFactory, - StoreManagerInterface $storeManager + StoreManagerInterface $storeManager, + \Magento\Eav\Model\Config $eavConfig = null ) { parent::__construct($context); $this->resultRawFactory = $resultRawFactory; $this->resultJsonFactory = $resultJsonFactory; $this->layoutFactory = $layoutFactory; $this->storeManager = $storeManager; + $this->eavConfig = $eavConfig + ?: \Magento\Framework\App\ObjectManager::getInstance()->get(\Magento\Eav\Model\Config::class); } /** * Filter category data * + * @deprecated * @param array $rawData * @return array */ protected function _filterCategoryPostData(array $rawData) { $data = $rawData; - // @todo It is a workaround to prevent saving this data in category model and it has to be refactored in future if (isset($data['image']) && is_array($data['image'])) { if (!empty($data['image']['delete'])) { $data['image'] = null; @@ -126,7 +136,7 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Category $this->storeManager->setCurrentStore($store->getCode()); $parentId = isset($categoryPostData['parent']) ? $categoryPostData['parent'] : null; if ($categoryPostData) { - $category->addData($this->_filterCategoryPostData($categoryPostData)); + $category->addData($categoryPostData); if ($isNewCategory) { $parentCategory = $this->getParentCategory($parentId, $storeId); $category->setPath($parentCategory->getPath()); @@ -248,18 +258,30 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Category } /** - * Image data preprocessing + * Sets image attribute data to false if image was removed * * @param array $data - * * @return array */ public function imagePreprocessing($data) { - if (empty($data['image'])) { - unset($data['image']); - $data['image']['delete'] = true; + $entityType = $this->eavConfig->getEntityType(CategoryAttributeInterface::ENTITY_TYPE_CODE); + + foreach ($entityType->getAttributeCollection() as $attributeModel) { + $attributeCode = $attributeModel->getAttributeCode(); + $backendModel = $attributeModel->getBackend(); + + if (isset($data[$attributeCode])) { + continue; + } + + if (!$backendModel instanceof \Magento\Catalog\Model\Category\Attribute\Backend\Image) { + continue; + } + + $data[$attributeCode] = false; } + return $data; } diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php index 8a2b76b1bbd56212f27cb45db587e38fc2bb57c8..c338937ae1be1df330d32c4992f3cb5839ebad05 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/AddAttributeToTemplate.php @@ -16,7 +16,6 @@ use Magento\Eav\Api\Data\AttributeGroupInterfaceFactory; use Magento\Eav\Api\Data\AttributeInterface; use Magento\Eav\Api\Data\AttributeSetInterface; use Magento\Framework\Api\SearchCriteriaBuilder; -use Magento\Framework\Api\SortOrderBuilder; use Magento\Framework\Exception\LocalizedException; use Psr\Log\LoggerInterface; use Magento\Framework\Api\ExtensionAttributesFactory; @@ -53,11 +52,6 @@ class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Produ */ protected $searchCriteriaBuilder; - /** - * @var SortOrderBuilder - */ - protected $sortOrderBuilder; - /** * @var AttributeGroupInterfaceFactory */ @@ -115,7 +109,6 @@ class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Produ $attributeGroupSearchCriteria = $this->getSearchCriteriaBuilder() ->addFilter('attribute_set_id', $attributeSet->getAttributeSetId()) ->addFilter('attribute_group_code', $groupCode) - ->addSortOrder($this->getSortOrderBuilder()->setAscendingDirection()->create()) ->setPageSize(1) ->create(); @@ -252,18 +245,6 @@ class AddAttributeToTemplate extends \Magento\Catalog\Controller\Adminhtml\Produ return $this->searchCriteriaBuilder; } - /** - * @return SortOrderBuilder - */ - private function getSortOrderBuilder() - { - if (null === $this->sortOrderBuilder) { - $this->sortOrderBuilder = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\Api\SortOrderBuilder::class); - } - return $this->sortOrderBuilder; - } - /** * @return AttributeManagementInterface */ diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php index d59492c4065e72706f8c1a168e4a33b1c2c9e825..4eed888b3cb08b18f07e403bc172ec9615108a09 100644 --- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php +++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php @@ -131,12 +131,7 @@ class Helper $productData[$field] = []; } } - - foreach ($productData['website_ids'] as $websiteId => $checkboxValue) { - if (!$checkboxValue) { - unset($productData['website_ids'][$websiteId]); - } - } + $productData['website_ids'] = $this->filterWebsiteIds($productData['website_ids']); $wasLockedMedia = false; if ($product->isLockedAttribute('media')) { @@ -422,4 +417,23 @@ class Helper } return $this->dateTimeFilter; } + + /** + * Remove ids of non selected websites from $websiteIds array and return filtered data + * $websiteIds parameter expects array with website ids as keys and 1 (selected) or 0 (non selected) as values + * Only one id (default website ID) will be set to $websiteIds array when the single store mode is turned on + * + * @param array $websiteIds + * @return array + */ + private function filterWebsiteIds($websiteIds) + { + if (!$this->storeManager->isSingleStoreMode()) { + $websiteIds = array_filter((array)$websiteIds); + } else { + $websiteIds[$this->storeManager->getWebsite(true)->getId()] = 1; + } + + return $websiteIds; + } } diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php index 9e855e05c7ce388dd52c11829882d5b46f634d02..906718c64c6e0bf814bda973c81168d067d887ed 100644 --- a/app/code/Magento/Catalog/Model/Category.php +++ b/app/code/Magento/Catalog/Model/Category.php @@ -652,14 +652,14 @@ class Category extends \Magento\Catalog\Model\AbstractModel implements } /** - * Retrieve image URL - * - * @return string + * @param string $attributeCode + * @return bool|string + * @throws \Magento\Framework\Exception\LocalizedException */ - public function getImageUrl() + public function getImageUrl($attributeCode = 'image') { $url = false; - $image = $this->getImage(); + $image = $this->getData($attributeCode); if ($image) { if (is_string($image)) { $url = $this->_storeManager->getStore()->getBaseUrl( diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php index 5eb4461ace5f0e88583337a9b8c2efe2c71bad64..5e8589428fad1d17abe4480abf1d16a8b727abcb 100644 --- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php +++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php @@ -21,8 +21,6 @@ class Image extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend protected $_uploaderFactory; /** - * Filesystem facade - * * @var \Magento\Framework\Filesystem * * @deprecated @@ -30,8 +28,6 @@ class Image extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend protected $_filesystem; /** - * File Uploader factory - * * @var \Magento\MediaStorage\Model\File\UploaderFactory * * @deprecated @@ -46,15 +42,16 @@ class Image extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend protected $_logger; /** - * Image uploader - * * @var \Magento\Catalog\Model\ImageUploader */ private $imageUploader; /** - * Image constructor. - * + * @var string + */ + private $additionalData = '_additional_data_'; + + /** * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Filesystem $filesystem * @param \Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory @@ -70,8 +67,44 @@ class Image extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend } /** - * Get image uploader + * Gets image name from $value array. + * Will return empty string in a case when $value is not an array + * + * @param array $value Attribute value + * @return string + */ + private function getUploadedImageName($value) + { + if (is_array($value) && isset($value[0]['name'])) { + return $value[0]['name']; + } + + return ''; + } + + /** + * Avoiding saving potential upload data to DB + * Will set empty image attribute value if image was not uploaded * + * @param \Magento\Framework\DataObject $object + * @return $this + */ + public function beforeSave($object) + { + $attributeName = $this->getAttribute()->getName(); + $value = $object->getData($attributeName); + + if ($imageName = $this->getUploadedImageName($value)) { + $object->setData($this->additionalData . $attributeName, $value); + $object->setData($attributeName, $imageName); + } else if (!is_string($value)) { + $object->setData($attributeName, ''); + } + + return parent::beforeSave($object); + } + + /** * @return \Magento\Catalog\Model\ImageUploader * * @deprecated @@ -79,10 +112,10 @@ class Image extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend private function getImageUploader() { if ($this->imageUploader === null) { - $this->imageUploader = \Magento\Framework\App\ObjectManager::getInstance()->get( - \Magento\Catalog\CategoryImageUpload::class - ); + $this->imageUploader = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Catalog\CategoryImageUpload::class); } + return $this->imageUploader; } @@ -94,15 +127,16 @@ class Image extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend */ public function afterSave($object) { - $image = $object->getData($this->getAttribute()->getName(), null); + $value = $object->getData($this->additionalData . $this->getAttribute()->getName()); - if ($image !== null) { + if ($imageName = $this->getUploadedImageName($value)) { try { - $this->getImageUploader()->moveFileFromTmp($image); + $this->getImageUploader()->moveFileFromTmp($imageName); } catch (\Exception $e) { $this->_logger->critical($e); } } + return $this; } } diff --git a/app/code/Magento/Catalog/Model/Category/DataProvider.php b/app/code/Magento/Catalog/Model/Category/DataProvider.php index 8626d183e2fac36e2b2fb194c418384d9ff87d82..aefd31e21ad62f8469837a801581170581555c6a 100644 --- a/app/code/Magento/Catalog/Model/Category/DataProvider.php +++ b/app/code/Magento/Catalog/Model/Category/DataProvider.php @@ -17,6 +17,7 @@ use Magento\Ui\Component\Form\Field; use Magento\Ui\DataProvider\EavValidationRules; use Magento\Catalog\Model\CategoryFactory; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Catalog\Model\Category\Attribute\Backend\Image as ImageBackendModel; /** * Class DataProvider @@ -206,11 +207,8 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider $categoryData = $this->addUseDefaultSettings($category, $categoryData); $categoryData = $this->addUseConfigSettings($categoryData); $categoryData = $this->filterFields($categoryData); - if (isset($categoryData['image'])) { - unset($categoryData['image']); - $categoryData['image'][0]['name'] = $category->getData('image'); - $categoryData['image'][0]['url'] = $category->getImageUrl(); - } + $categoryData = $this->convertValues($category, $categoryData); + $this->loadedData[$category->getId()] = $categoryData; } return $this->loadedData; @@ -371,6 +369,31 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider return array_diff_key($categoryData, array_flip($this->ignoreFields)); } + /** + * Converts category image data to acceptable for rendering format + * + * @param \Magento\Catalog\Model\Category $category + * @param array $categoryData + * @return array + */ + private function convertValues($category, $categoryData) + { + foreach ($category->getAttributes() as $attributeCode => $attribute) { + if (!isset($categoryData[$attributeCode])) { + continue; + } + + if ($attribute->getBackend() instanceof ImageBackendModel) { + unset($categoryData[$attributeCode]); + + $categoryData[$attributeCode][0]['name'] = $category->getData($attributeCode); + $categoryData[$attributeCode][0]['url'] = $category->getImageUrl($attributeCode); + } + } + + return $categoryData; + } + /** * Category's fields default values * diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php index 9e9f18e0113717ad6853ea5d4512661a897e926c..c913c82de1acdecbf3328c37fe7529dfbab193ba 100644 --- a/app/code/Magento/Catalog/Model/Product.php +++ b/app/code/Magento/Catalog/Model/Product.php @@ -22,7 +22,6 @@ use Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryExtensionFactory; * @method Product setHasError(bool $value) * @method \Magento\Catalog\Model\ResourceModel\Product getResource() * @method null|bool getHasError() - * @method Product setAssociatedProductIds(array $productIds) * @method array getAssociatedProductIds() * @method Product setNewVariationsAttributeSetId(int $value) * @method int getNewVariationsAttributeSetId() @@ -2614,4 +2613,16 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements } return $this->mediaGalleryProcessor; } + + /** + * Set the associated products + * + * @param array $productIds + * @return $this + */ + public function setAssociatedProductIds(array $productIds) + { + $this->getExtensionAttributes()->setConfigurableProductLinks($productIds); + return $this; + } } diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php index 5c3fd4730aaed4907cbf197f6a5298060ad12acf..0bb468b77ee6ea18df11e7fd349e8315c6f74318 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Stock.php @@ -59,7 +59,7 @@ class Stock extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend if (isset($stockData['qty']) && $stockData['qty'] === '') { $stockData['qty'] = null; } - if ($object->getStockData() !== null || $stockData !== null) { + if ($object->getStockData() !== null && $stockData !== null) { $object->setStockData(array_replace((array)$object->getStockData(), (array)$stockData)); } $object->unsetData($this->getAttribute()->getAttributeCode()); diff --git a/app/code/Magento/Catalog/Model/Product/Image.php b/app/code/Magento/Catalog/Model/Product/Image.php index 7d8b464db3b34cd116d2c8c7adae86113040e67f..34e1ad30ad434add33508bb63c7b8b1a78ddc9fb 100644 --- a/app/code/Magento/Catalog/Model/Product/Image.php +++ b/app/code/Magento/Catalog/Model/Product/Image.php @@ -498,8 +498,7 @@ class Image extends \Magento\Framework\Model\AbstractModel $path = [ $this->_catalogProductMediaConfig->getBaseMediaPath(), 'cache', - $this->_storeManager->getStore()->getId(), - $path[] = $this->getDestinationSubdir(), + $this->getDestinationSubdir(), ]; if (!empty($this->_width) || !empty($this->_height)) { $path[] = "{$this->_width}x{$this->_height}"; diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php index 4385c8add37aef25dc52a888d2ff839b49e734db..27796e1cda2a167848c2a3b8fb0d096f40c172d6 100644 --- a/app/code/Magento/Catalog/Model/ProductRepository.php +++ b/app/code/Magento/Catalog/Model/ProductRepository.php @@ -640,6 +640,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa $collection->load(); + $collection->addCategoryIds(); $searchResult = $this->searchResultsFactory->create(); $searchResult->setSearchCriteria($searchCriteria); $searchResult->setItems($collection->getItems()); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..e6a995b654703402175f79ccc6e50b5172db5fd4 --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/BaseSelectProcessorInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\ResourceModel\Product; + +use Magento\Framework\DB\Select; + +/** + * Interface BaseSelectProcessorInterface + * @api + */ +interface BaseSelectProcessorInterface +{ + /** + * Product table alias + */ + const PRODUCT_TABLE_ALIAS = 'child'; + + /** + * @param Select $select + * @return Select + */ + public function process(Select $select); +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index b2b20b9d223e223a093d7f6ae222e19631084528..c24460981afbf8d6c6cccb79255031d8320b726f 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -2182,12 +2182,17 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac $mediaGalleries = []; $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); + $items = $this->getItems(); + + $select->where('entity.' . $linkField . ' IN (?)', array_map(function ($item) { + return $item->getId(); + }, $items)); foreach ($this->getConnection()->fetchAll($select) as $row) { $mediaGalleries[$row[$linkField]][] = $row; } - foreach ($this->getItems() as $item) { + foreach ($items as $item) { $mediaEntries = isset($mediaGalleries[$item->getId()]) ? $mediaGalleries[$item->getId()] : []; $this->getGalleryReadHandler()->addMediaDataToProduct($item, $mediaEntries); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/CompositeBaseSelectProcessor.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/CompositeBaseSelectProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..86f5a23708400ef40a58677f07e57552a39bd6bd --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/CompositeBaseSelectProcessor.php @@ -0,0 +1,49 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\ResourceModel\Product; + +use Magento\Framework\DB\Select; +use Magento\Framework\Exception\InputException; + +/** + * Class CompositeBaseSelectProcessor + */ +class CompositeBaseSelectProcessor implements BaseSelectProcessorInterface +{ + /** + * @var BaseSelectProcessorInterface[] + */ + private $baseSelectProcessors; + + /** + * @param BaseSelectProcessorInterface[] $baseSelectProcessors + * @throws InputException + */ + public function __construct( + array $baseSelectProcessors + ) { + foreach ($baseSelectProcessors as $baseSelectProcessor) { + if (!$baseSelectProcessor instanceof BaseSelectProcessorInterface) { + throw new InputException( + __('Processor %1 doesn\'t implement BaseSelectProcessorInterface', get_class($baseSelectProcessor)) + ); + } + } + $this->baseSelectProcessors = $baseSelectProcessors; + } + + /** + * @param Select $select + * @return Select + */ + public function process(Select $select) + { + foreach ($this->baseSelectProcessors as $baseSelectProcessor) { + $select = $baseSelectProcessor->process($select); + } + return $select; + } +} diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php index d9612f18c9111df4634e18cb8b9abf523643f2e5..432bc696ef7cecd822b5168e7da70a9ed1cd4fc5 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/AbstractEav.php @@ -197,7 +197,7 @@ abstract class AbstractEav extends \Magento\Catalog\Model\ResourceModel\Product\ )->joinLeft( ['e' => $this->getTable('catalog_product_entity')], 'e.' . $linkField .' = l.parent_id', - ['e.entity_id as parent_id'] + [] )->join( ['cs' => $this->getTable('store')], '', @@ -205,9 +205,17 @@ abstract class AbstractEav extends \Magento\Catalog\Model\ResourceModel\Product\ )->join( ['i' => $idxTable], 'l.child_id = i.entity_id AND cs.store_id = i.store_id', - ['attribute_id', 'store_id', 'value'] + [] )->group( - ['parent_id', 'i.attribute_id', 'i.store_id', 'i.value'] + ['parent_id', 'i.attribute_id', 'i.store_id', 'i.value', 'l.child_id'] + )->columns( + [ + 'parent_id' => 'e.entity_id', + 'attribute_id' => 'i.attribute_id', + 'store_id' => 'i.store_id', + 'value' => 'i.value', + 'source_id' => 'l.child_id' + ] ); if ($parentIds !== null) { $select->where('e.entity_id IN(?)', $parentIds); @@ -222,7 +230,7 @@ abstract class AbstractEav extends \Magento\Catalog\Model\ResourceModel\Product\ 'select' => $select, 'entity_field' => new \Zend_Db_Expr('l.parent_id'), 'website_field' => new \Zend_Db_Expr('cs.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'store_field' => new \Zend_Db_Expr('cs.store_id'), ] ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php index e8d9889e68d59384e74cfb3fc0491a2edb8f3edb..a45d4f13a1a9a811fffb6269bf94575675c60d80 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Eav/Decimal.php @@ -85,6 +85,7 @@ class Decimal extends AbstractEav 'pdd.attribute_id', 'cs.store_id', 'value' => $productValueExpression, + 'source_id' => 'cpe.entity_id', ] ); @@ -116,7 +117,7 @@ class Decimal extends AbstractEav 'select' => $select, 'entity_field' => new \Zend_Db_Expr('cpe.entity_id'), 'website_field' => new \Zend_Db_Expr('cs.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'store_field' => new \Zend_Db_Expr('cs.store_id'), ] ); 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 c4eda1c987192acb5dd7e5ce003fe8b9cff0bdc6..1d37c57aa8b251bc73edf6e4a06aed1789cdc476 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 @@ -178,6 +178,7 @@ class Source extends AbstractEav 'pid.attribute_id', 'pid.store_id', 'value' => $ifNullSql, + 'pid.entity_id', ] )->where( 'pid.attribute_id IN(?)', @@ -200,7 +201,7 @@ class Source extends AbstractEav 'select' => $select, 'entity_field' => new \Zend_Db_Expr('pid.entity_id'), 'website_field' => new \Zend_Db_Expr('pid.website_id'), - 'store_field' => new \Zend_Db_Expr('pid.store_id') + 'store_field' => new \Zend_Db_Expr('pid.store_id'), ] ); $query = $select->insertFromSelect($idxTable); @@ -221,11 +222,7 @@ class Source extends AbstractEav $connection = $this->getConnection(); // prepare multiselect attributes - if ($attributeId === null) { - $attrIds = $this->_getIndexableAttributes(true); - } else { - $attrIds = [$attributeId]; - } + $attrIds = $attributeId === null ? $this->_getIndexableAttributes(true) : [$attributeId]; if (!$attrIds) { return $this; @@ -247,20 +244,20 @@ class Source extends AbstractEav $productValueExpression = $connection->getCheckSql('pvs.value_id > 0', 'pvs.value', 'pvd.value'); $select = $connection->select()->from( ['pvd' => $this->getTable('catalog_product_entity_varchar')], - [$productIdField, 'attribute_id'] + [] )->join( ['cs' => $this->getTable('store')], '', - ['store_id'] + [] )->joinLeft( ['pvs' => $this->getTable('catalog_product_entity_varchar')], "pvs.{$productIdField} = pvd.{$productIdField} AND pvs.attribute_id = pvd.attribute_id" . ' AND pvs.store_id=cs.store_id', - ['value' => $productValueExpression] + [] )->joinLeft( ['cpe' => $this->getTable('catalog_product_entity')], "cpe.{$productIdField} = pvd.{$productIdField}", - ['entity_id'] + [] )->where( 'pvd.store_id=?', $connection->getIfNullSql('pvs.store_id', \Magento\Store\Model\Store::DEFAULT_STORE_ID) @@ -272,6 +269,14 @@ class Source extends AbstractEav $attrIds )->where( 'cpe.entity_id IS NOT NULL' + )->columns( + [ + 'entity_id' => 'cpe.entity_id', + 'attribute_id' => 'attribute_id', + 'store_id' => 'cs.store_id', + 'value' => $productValueExpression, + 'source_id' => 'cpe.entity_id', + ] ); $statusCond = $connection->quoteInto('=?', ProductStatus::STATUS_ENABLED); @@ -289,30 +294,11 @@ class Source extends AbstractEav 'select' => $select, 'entity_field' => new \Zend_Db_Expr('cpe.entity_id'), 'website_field' => new \Zend_Db_Expr('cs.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'store_field' => new \Zend_Db_Expr('cs.store_id'), ] ); - $i = 0; - $data = []; - $query = $select->query(); - while ($row = $query->fetch()) { - $values = explode(',', $row['value']); - foreach ($values as $valueId) { - if (isset($options[$row['attribute_id']][$valueId])) { - $data[] = [$row['entity_id'], $row['attribute_id'], $row['store_id'], $valueId]; - $i++; - if ($i % 10000 == 0) { - $this->_saveIndexData($data); - $data = []; - } - } - } - } - - $this->_saveIndexData($data); - unset($options); - unset($data); + $this->saveDataFromSelect($select, $options); return $this; } @@ -331,7 +317,7 @@ class Source extends AbstractEav $connection = $this->getConnection(); $connection->insertArray( $this->getIdxTable(), - ['entity_id', 'attribute_id', 'store_id', 'value'], + ['entity_id', 'attribute_id', 'store_id', 'value', 'source_id'], $data ); return $this; @@ -348,4 +334,31 @@ class Source extends AbstractEav { return $this->tableStrategy->getTableName('catalog_product_index_eav'); } + + /** + * @param \Magento\Framework\DB\Select $select + * @param array $options + * @return void + */ + private function saveDataFromSelect(\Magento\Framework\DB\Select $select, array $options) + { + $i = 0; + $data = []; + $query = $select->query(); + while ($row = $query->fetch()) { + $values = explode(',', $row['value']); + foreach ($values as $valueId) { + if (isset($options[$row['attribute_id']][$valueId])) { + $data[] = [$row['entity_id'], $row['attribute_id'], $row['store_id'], $valueId, $row['source_id']]; + $i++; + if ($i % 10000 == 0) { + $this->_saveIndexData($data); + $data = []; + } + } + } + } + + $this->_saveIndexData($data); + } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php index ea72691ea003939fc277299bee214da316f4f757..3aa6642c82d845eb6ca1143f9c82484c53c659f9 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php @@ -6,7 +6,8 @@ namespace Magento\Catalog\Model\ResourceModel\Product\Indexer; use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; @@ -32,22 +33,31 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild */ private $metadataPool; + /** + * @var BaseSelectProcessorInterface + */ + private $baseSelectProcessor; + /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\App\ResourceConnection $resourceConnection * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param BaseSelectProcessorInterface $baseSelectProcessor */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Customer\Model\Session $customerSession, - \Magento\Framework\EntityManager\MetadataPool $metadataPool + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + BaseSelectProcessorInterface $baseSelectProcessor = null ) { $this->storeManager = $storeManager; $this->resource = $resourceConnection; $this->customerSession = $customerSession; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -58,24 +68,27 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); - return [$this->resource->getConnection()->select() + $priceSelect = $this->resource->getConnection()->select() ->from(['parent' => $productTable], '') ->joinInner( ['link' => $this->resource->getTableName('catalog_product_relation')], "link.parent_id = parent.$linkField", [] )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), ['entity_id'] )->joinInner( ['t' => $this->resource->getTableName('catalog_product_index_price')], - 't.entity_id = child.entity_id', + sprintf('t.entity_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] - )->where('parent.entity_id = ? ', $productId) + )->where('parent.entity_id = ?', $productId) ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()) ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->order('t.min_price ' . Select::SQL_ASC) - ->limit(1)]; + ->limit(1); + $priceSelect = $this->baseSelectProcessor->process($priceSelect); + + return [$priceSelect]; } } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php index 289445ae2daf07c4132f6d69372c84fcfb36a066..2b979ff79fe5c498215b6b144e71897b9ca2c913 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php @@ -368,7 +368,7 @@ class DefaultPrice extends AbstractIndexer implements PriceInterface 'select' => $select, 'entity_field' => new \Zend_Db_Expr('e.entity_id'), 'website_field' => new \Zend_Db_Expr('cw.website_id'), - 'store_field' => new \Zend_Db_Expr('cs.store_id') + 'store_field' => new \Zend_Db_Expr('cs.store_id'), ] ); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php index d325ab1a9a08d1175c0fedb2a94c10ac5d7dc3c2..7caa72b367979bc4ecbded5641490a59f7835928 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php @@ -7,6 +7,7 @@ namespace Magento\Catalog\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Store\Model\Store; @@ -37,25 +38,34 @@ class LinkedProductSelectBuilderByBasePrice implements LinkedProductSelectBuilde */ private $metadataPool; + /** + * @var BaseSelectProcessorInterface + */ + private $baseSelectProcessor; + /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\App\ResourceConnection $resourceConnection * @param \Magento\Eav\Model\Config $eavConfig * @param \Magento\Catalog\Helper\Data $catalogHelper * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param BaseSelectProcessorInterface $baseSelectProcessor */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Eav\Model\Config $eavConfig, \Magento\Catalog\Helper\Data $catalogHelper, - \Magento\Framework\EntityManager\MetadataPool $metadataPool + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + BaseSelectProcessorInterface $baseSelectProcessor = null ) { $this->storeManager = $storeManager; $this->resource = $resourceConnection; $this->eavConfig = $eavConfig; $this->catalogHelper = $catalogHelper; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -74,18 +84,19 @@ class LinkedProductSelectBuilderByBasePrice implements LinkedProductSelectBuilde "link.parent_id = parent.$linkField", [] )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS, $linkField), ['entity_id'] )->joinInner( ['t' => $priceAttribute->getBackendTable()], - "t.$linkField = child.$linkField", + sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] - )->where('parent.entity_id = ? ', $productId) + )->where('parent.entity_id = ?', $productId) ->where('t.attribute_id = ?', $priceAttribute->getAttributeId()) ->where('t.value IS NOT NULL') ->order('t.value ' . Select::SQL_ASC) ->limit(1); + $priceSelect = $this->baseSelectProcessor->process($priceSelect); $priceSelectDefault = clone $priceSelect; $priceSelectDefault->where('t.store_id = ?', Store::DEFAULT_STORE_ID); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php index 792a8f5b86d102204a93491c0413ae2839b16a73..68eaf206e293fea3e90c04e9dfb7bc0329704162 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderBySpecialPrice.php @@ -7,9 +7,13 @@ namespace Magento\Catalog\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Store\Model\Store; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBuilderInterface { /** @@ -47,6 +51,11 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui */ private $metadataPool; + /** + * @var BaseSelectProcessorInterface + */ + private $baseSelectProcessor; + /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\App\ResourceConnection $resourceConnection @@ -55,6 +64,7 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui * @param \Magento\Framework\Stdlib\DateTime $dateTime * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param BaseSelectProcessorInterface $baseSelectProcessor */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, @@ -63,7 +73,8 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui \Magento\Catalog\Helper\Data $catalogHelper, \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, - \Magento\Framework\EntityManager\MetadataPool $metadataPool + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + BaseSelectProcessorInterface $baseSelectProcessor = null ) { $this->storeManager = $storeManager; $this->resource = $resourceConnection; @@ -72,6 +83,8 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui $this->dateTime = $dateTime; $this->localeDate = $localeDate; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -95,12 +108,12 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui "link.parent_id = parent.$linkField", [] )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), ['entity_id'] )->joinInner( ['t' => $specialPriceAttribute->getBackendTable()], - "t.$linkField = child.$linkField", + sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] )->joinLeft( ['special_from' => $specialPriceFromDate->getBackendTable()], @@ -116,7 +129,7 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui $specialPriceToDate->getAttributeId() ), '' - )->where('parent.entity_id = ? ', $productId) + )->where('parent.entity_id = ?', $productId) ->where('t.attribute_id = ?', $specialPriceAttribute->getAttributeId()) ->where('t.value IS NOT NULL') ->where( @@ -127,6 +140,7 @@ class LinkedProductSelectBuilderBySpecialPrice implements LinkedProductSelectBui $currentDate )->order('t.value ' . Select::SQL_ASC) ->limit(1); + $specialPrice = $this->baseSelectProcessor->process($specialPrice); $specialPriceDefault = clone $specialPrice; $specialPriceDefault->where('t.store_id = ?', Store::DEFAULT_STORE_ID); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php index d2d6d89c0a2a5828290e1191147451e4f6d20b7b..25bf83f837de72cec501379378e6960084d6b1fd 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByTierPrice.php @@ -6,7 +6,7 @@ namespace Magento\Catalog\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Model\Product; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; class LinkedProductSelectBuilderByTierPrice implements LinkedProductSelectBuilderInterface @@ -41,25 +41,34 @@ class LinkedProductSelectBuilderByTierPrice implements LinkedProductSelectBuilde */ private $metadataPool; + /** + * @var BaseSelectProcessorInterface + */ + private $baseSelectProcessor; + /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\App\ResourceConnection $resourceConnection * @param \Magento\Customer\Model\Session $customerSession * @param \Magento\Catalog\Helper\Data $catalogHelper * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param BaseSelectProcessorInterface $baseSelectProcessor */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, \Magento\Framework\App\ResourceConnection $resourceConnection, \Magento\Customer\Model\Session $customerSession, \Magento\Catalog\Helper\Data $catalogHelper, - \Magento\Framework\EntityManager\MetadataPool $metadataPool + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + BaseSelectProcessorInterface $baseSelectProcessor = null ) { $this->storeManager = $storeManager; $this->resource = $resourceConnection; $this->customerSession = $customerSession; $this->catalogHelper = $catalogHelper; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -77,18 +86,19 @@ class LinkedProductSelectBuilderByTierPrice implements LinkedProductSelectBuilde "link.parent_id = parent.$linkField", [] )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), ['entity_id'] )->joinInner( ['t' => $this->resource->getTableName('catalog_product_entity_tier_price')], - "t.$linkField = child.$linkField", + sprintf('t.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), [] - )->where('parent.entity_id = ? ', $productId) + )->where('parent.entity_id = ?', $productId) ->where('t.all_groups = 1 OR customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.qty = ?', 1) ->order('t.value ' . Select::SQL_ASC) ->limit(1); + $priceSelect = $this->baseSelectProcessor->process($priceSelect); $priceSelectDefault = clone $priceSelect; $priceSelectDefault->where('t.website_id = ?', self::DEFAULT_WEBSITE_ID); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..656998113fdb92e50e5177db4876651cfcd2023a --- /dev/null +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/StatusBaseSelectProcessor.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Model\ResourceModel\Product; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Eav\Model\Config; +use Magento\Framework\DB\Select; +use Magento\Framework\EntityManager\MetadataPool; + +/** + * Class StatusBaseSelectProcessor + */ +class StatusBaseSelectProcessor implements BaseSelectProcessorInterface +{ + /** + * @var Config + */ + private $eavConfig; + + /** + * @var MetadataPool + */ + private $metadataPool; + + /** + * @param Config $eavConfig + * @param MetadataPool $metadataPool + */ + public function __construct( + Config $eavConfig, + MetadataPool $metadataPool + ) { + $this->eavConfig = $eavConfig; + $this->metadataPool = $metadataPool; + } + + /** + * @param Select $select + * @return Select + */ + public function process(Select $select) + { + $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); + $statusAttribute = $this->eavConfig->getAttribute(Product::ENTITY, ProductInterface::STATUS); + + $select->join( + ['status_attr' => $statusAttribute->getBackendTable()], + sprintf('status_attr.%s = %s.%1$s', $linkField, self::PRODUCT_TABLE_ALIAS), + [] + ) + ->where('status_attr.attribute_id = ?', $statusAttribute->getAttributeId()) + ->where('status_attr.value = ?', Status::STATUS_ENABLED); + + return $select; + } +} diff --git a/app/code/Magento/Catalog/Model/System/Config/Backend/Catalog/Url/Rewrite/Suffix.php b/app/code/Magento/Catalog/Model/System/Config/Backend/Catalog/Url/Rewrite/Suffix.php index 5ed18d15b2f304cdc9ca6e89a7bcc09e2a5c3590..c1f37eca1c55c7b81cf4b14a034738c5211b7085 100644 --- a/app/code/Magento/Catalog/Model/System/Config/Backend/Catalog/Url/Rewrite/Suffix.php +++ b/app/code/Magento/Catalog/Model/System/Config/Backend/Catalog/Url/Rewrite/Suffix.php @@ -40,6 +40,11 @@ class Suffix extends \Magento\Framework\App\Config\Value */ protected $resource; + /** + * @var \Magento\Framework\App\Config\ScopePool + */ + private $scopePool; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -75,6 +80,22 @@ class Suffix extends \Magento\Framework\App\Config\Value $this->resource = $appResource; } + /** + * Get instance of ScopePool + * + * @return \Magento\Framework\App\Config\ScopePool + * @deprecated + */ + private function getScopePool() + { + if ($this->scopePool === null) { + $this->scopePool = \Magento\Framework\App\ObjectManager::getInstance()->get( + \Magento\Framework\App\Config\ScopePool::class + ); + } + return $this->scopePool; + } + /** * Check url rewrite suffix - whether we can support it * @@ -103,6 +124,24 @@ class Suffix extends \Magento\Framework\App\Config\Value return parent::afterSave(); } + /** + * {@inheritdoc} + */ + public function afterDeleteCommit() + { + if ($this->isValueChanged()) { + $this->updateSuffixForUrlRewrites(); + if ($this->isCategorySuffixChanged()) { + $this->cacheTypeList->invalidate([ + \Magento\Framework\App\Cache\Type\Block::TYPE_IDENTIFIER, + \Magento\Framework\App\Cache\Type\Collection::TYPE_IDENTIFIER + ]); + } + } + + return parent::afterDeleteCommit(); + } + /** * Check is category suffix changed * @@ -135,7 +174,12 @@ class Suffix extends \Magento\Framework\App\Config\Value } $entities = $this->urlFinder->findAllByData($dataFilter); $oldSuffixPattern = '~' . preg_quote($this->getOldValue()) . '$~'; - $suffix = $this->getValue(); + if ($this->getValue() !== null) { + $suffix = $this->getValue(); + } else { + $this->getScopePool()->clean(); + $suffix = $this->_config->getValue($this->getPath()); + } foreach ($entities as $urlRewrite) { $bind = $urlRewrite->getIsAutogenerated() ? [UrlRewrite::REQUEST_PATH => preg_replace($oldSuffixPattern, $suffix, $urlRewrite->getRequestPath())] diff --git a/app/code/Magento/Catalog/Setup/InstallSchema.php b/app/code/Magento/Catalog/Setup/InstallSchema.php index 2ccd8af79a5da7eb51f43aeabfa19a9de14c81c1..a2ba6aa283f6516021f0baffaafd6e6470c060f0 100644 --- a/app/code/Magento/Catalog/Setup/InstallSchema.php +++ b/app/code/Magento/Catalog/Setup/InstallSchema.php @@ -18,6 +18,7 @@ class InstallSchema implements InstallSchemaInterface /** * {@inheritdoc} * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws \Zend_Db_Exception */ public function install(SchemaSetupInterface $setup, ModuleContextInterface $context) { @@ -1853,6 +1854,9 @@ class InstallSchema implements InstallSchemaInterface $installer->getConnection() ->createTable($table); + $customerGroupTable = $setup->getConnection()->describeTable($setup->getTable('customer_group')); + $customerGroupIdType = $customerGroupTable['customer_group_id']['DATA_TYPE'] == 'int' + ? \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER : $customerGroupTable['customer_group_id']['DATA_TYPE']; /** * Create table 'catalog_product_entity_tier_price' */ @@ -1883,7 +1887,7 @@ class InstallSchema implements InstallSchemaInterface ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'default' => '0'], 'Customer Group ID' @@ -2426,7 +2430,6 @@ class InstallSchema implements InstallSchemaInterface 'option_id', $installer->getTable('catalog_product_option'), 'option_id', - \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE, \Magento\Framework\DB\Ddl\Table::ACTION_CASCADE ) ->setComment( @@ -2937,7 +2940,7 @@ class InstallSchema implements InstallSchemaInterface ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Customer Group ID' @@ -3056,7 +3059,7 @@ class InstallSchema implements InstallSchemaInterface ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Customer Group ID' diff --git a/app/code/Magento/Catalog/Setup/UpgradeSchema.php b/app/code/Magento/Catalog/Setup/UpgradeSchema.php old mode 100644 new mode 100755 index 9683632f121b6af18a3397e53bac2943521683c9..7fc2ef7d219ba917aa59c9887749a9024bda49ce --- a/app/code/Magento/Catalog/Setup/UpgradeSchema.php +++ b/app/code/Magento/Catalog/Setup/UpgradeSchema.php @@ -36,9 +36,81 @@ class UpgradeSchema implements UpgradeSchemaInterface if (version_compare($context->getVersion(), '2.1.0', '<')) { $this->addPercentageValueColumn($setup); } + + if (version_compare($context->getVersion(), '2.1.1', '<')) { + $tables = [ + 'catalog_product_index_price_cfg_opt_agr_idx', + 'catalog_product_index_price_cfg_opt_agr_tmp', + 'catalog_product_index_price_cfg_opt_idx', + 'catalog_product_index_price_cfg_opt_tmp', + 'catalog_product_index_price_final_idx', + 'catalog_product_index_price_final_tmp', + 'catalog_product_index_price_idx', + 'catalog_product_index_price_opt_agr_idx', + 'catalog_product_index_price_opt_agr_tmp', + 'catalog_product_index_price_opt_idx', + 'catalog_product_index_price_opt_tmp', + 'catalog_product_index_price_tmp', + ]; + foreach ($tables as $table) { + $setup->getConnection()->modifyColumn( + $setup->getTable($table), + 'customer_group_id', + ['type' => 'integer', 'nullable' => false] + ); + } + } + + if (version_compare($context->getVersion(), '2.1.2', '<')) { + $this->addSourceEntityIdToProductEavIndex($setup); + } + $setup->endSetup(); } + /** + * Add the column 'source_id' to the Product EAV index tables. + * It allows to identify which entity was used to create value in the index. + * It is useful to identify original entity in a composite products. + * + * @param SchemaSetupInterface $setup + * @return void + */ + private function addSourceEntityIdToProductEavIndex(SchemaSetupInterface $setup) + { + $tables = [ + 'catalog_product_index_eav', + 'catalog_product_index_eav_idx', + 'catalog_product_index_eav_tmp', + 'catalog_product_index_eav_decimal', + 'catalog_product_index_eav_decimal_idx', + 'catalog_product_index_eav_decimal_tmp', + ]; + $connection = $setup->getConnection(); + foreach ($tables as $tableName) { + $tableName = $setup->getTable($tableName); + $connection->addColumn( + $tableName, + 'source_id', + [ + 'type' => \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, + 'unsigned' => true, + 'nullable' => false, + 'default' => 0, + 'comment' => 'Original entity Id for attribute value', + ] + ); + $connection->dropIndex($tableName, $connection->getPrimaryKeyName($tableName)); + $primaryKeyFields = ['entity_id', 'attribute_id', 'store_id', 'value', 'source_id']; + $setup->getConnection()->addIndex( + $tableName, + $connection->getIndexName($tableName, $primaryKeyFields), + $primaryKeyFields, + \Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY + ); + } + } + /** * @param SchemaSetupInterface $setup * @return void diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/Image/UploadTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/Image/UploadTest.php new file mode 100644 index 0000000000000000000000000000000000000000..387e9a8a6a891b305170253befcd5cedb046feeb --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/Image/UploadTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Category\Image; + +use Magento\Catalog\Controller\Adminhtml\Category\Image\Upload as Model; +use Magento\Framework\App\Request\Http as Request; +use Magento\Catalog\Model\ImageUploader; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\DataObject; + +/** + * Class UploadTest + */ +class UploadTest extends \PHPUnit_Framework_TestCase +{ + private $objectManager; + + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + } + + public function executeDataProvider() + { + return [ + ['image1', 'image1'], + ['image2', 'image2'], + [null, 'image'], + ]; + } + + /** + * @param string $name + * @param string $savedName + * + * @dataProvider executeDataProvider + */ + public function testExecute($name, $savedName) + { + $request = $this->objectManager->getObject(Request::class); + + $uploader = $this->getMock(ImageUploader::class, ['saveFileToTmpDir'], [], '', false); + + $resultFactory = $this->getMock(ResultFactory::class, ['create'], [], '', false); + + $resultFactory->expects($this->once()) + ->method('create') + ->will($this->returnValue(new DataObject())); + + $model = $this->objectManager->getObject(Model::class, [ + 'request' => $request, + 'resultFactory' => $resultFactory, + 'imageUploader' => $uploader + ]); + + $uploader->expects($this->once()) + ->method('saveFileToTmpDir') + ->with($savedName) + ->will($this->returnValue([])); + + $request->setParam('param_name', $name); + + $model->execute(); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php index 51d99f7219575c205b61de6e5d93727633874f79..745d46710053a7c877e1bf05db47a715175b2bcb 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Category/SaveTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Category; +use \Magento\Catalog\Controller\Adminhtml\Category\Save as Model; + /** * Class SaveTest * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -14,67 +16,47 @@ class SaveTest extends \PHPUnit_Framework_TestCase /** * @var \Magento\Backend\Model\View\Result\RedirectFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $resultRedirectFactoryMock; - - /** - * @var \Magento\Framework\Controller\Result\RawFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $resultRawFactoryMock; + private $resultRedirectFactoryMock; /** * @var \Magento\Framework\Controller\Result\JsonFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $resultJsonFactoryMock; + private $resultJsonFactoryMock; /** * @var \Magento\Framework\View\LayoutFactory|\PHPUnit_Framework_MockObject_MockObject */ - protected $layoutFactoryMock; - - /** - * @var \Magento\Backend\App\Action\Context|\PHPUnit_Framework_MockObject_MockObject - */ - protected $contextMock; - - /** - * @var \Magento\Framework\View\Page\Title|\PHPUnit_Framework_MockObject_MockObject - */ - protected $titleMock; + private $layoutFactoryMock; /** * @var \Magento\Framework\App\RequestInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $requestMock; + private $requestMock; /** * @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $objectManagerMock; + private $objectManagerMock; /** * @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $eventManagerMock; - - /** - * @var \Magento\Framework\App\ResponseInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $responseMock; + private $eventManagerMock; /** * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $messageManagerMock; + private $messageManagerMock; /** * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ - protected $objectManager; + private $objectManager; /** * @var \Magento\Catalog\Controller\Adminhtml\Category\Save */ - protected $save; + private $save; /** * Set up @@ -84,24 +66,7 @@ class SaveTest extends \PHPUnit_Framework_TestCase */ protected function setUp() { - $this->markTestSkipped('Due to MAGETWO-48956'); $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->contextMock = $this->getMock( - \Magento\Backend\App\Action\Context::class, - [ - 'getTitle', - 'getRequest', - 'getObjectManager', - 'getEventManager', - 'getResponse', - 'getMessageManager', - 'getResultRedirectFactory' - ], - [], - '', - false - ); $this->resultRedirectFactoryMock = $this->getMock( \Magento\Backend\Model\View\Result\RedirectFactory::class, ['create'], @@ -109,13 +74,6 @@ class SaveTest extends \PHPUnit_Framework_TestCase '', false ); - $this->resultRawFactoryMock = $this->getMock( - \Magento\Framework\Controller\Result\RawFactory::class, - [], - [], - '', - false - ); $this->resultJsonFactoryMock = $this->getMock( \Magento\Framework\Controller\Result\JsonFactory::class, ['create'], @@ -151,12 +109,6 @@ class SaveTest extends \PHPUnit_Framework_TestCase true, ['dispatch'] ); - $this->responseMock = $this->getMockForAbstractClass( - \Magento\Framework\App\ResponseInterface::class, - [], - '', - false - ); $this->messageManagerMock = $this->getMockForAbstractClass( \Magento\Framework\Message\ManagerInterface::class, [], @@ -167,23 +119,15 @@ class SaveTest extends \PHPUnit_Framework_TestCase ['addSuccess', 'getMessages'] ); - $this->contextMock->expects($this->any())->method('getTitle')->willReturn($this->titleMock); - $this->contextMock->expects($this->any())->method('getRequest')->willReturn($this->requestMock); - $this->contextMock->expects($this->any())->method('getObjectManager')->willReturn($this->objectManagerMock); - $this->contextMock->expects($this->any())->method('getEventManager')->willReturn($this->eventManagerMock); - $this->contextMock->expects($this->any())->method('getResponse')->willReturn($this->responseMock); - $this->contextMock->expects($this->any())->method('getMessageManager')->willReturn($this->messageManagerMock); - $this->contextMock->expects($this->any()) - ->method('getResultRedirectFactory') - ->willReturn($this->resultRedirectFactoryMock); - $this->save = $this->objectManager->getObject( \Magento\Catalog\Controller\Adminhtml\Category\Save::class, [ - 'context' => $this->contextMock, - 'resultRawFactory' => $this->resultRawFactoryMock, + 'request' => $this->requestMock, + 'eventManager' => $this->eventManagerMock, + 'messageManager' => $this->messageManagerMock, 'resultJsonFactory' => $this->resultJsonFactoryMock, - 'layoutFactory' => $this->layoutFactoryMock + 'layoutFactory' => $this->layoutFactoryMock, + 'resultRedirectFactory' => $this->resultRedirectFactoryMock ] ); } @@ -201,6 +145,8 @@ class SaveTest extends \PHPUnit_Framework_TestCase */ public function testExecute($categoryId, $storeId, $parentId) { + $this->markTestSkipped('Due to MAGETWO-48956'); + $rootCategoryId = \Magento\Catalog\Model\Category::TREE_ROOT_ID; $products = [['any_product']]; $postData = [ @@ -577,4 +523,95 @@ class SaveTest extends \PHPUnit_Framework_TestCase ] ]; } + + /** + * @return array + */ + public function imagePreprocessingDataProvider() + { + return [ + [['attribute1' => null, 'attribute2' => 123]], + [['attribute2' => 123]] + ]; + } + + /** + * @dataProvider imagePreprocessingDataProvider + * + * @param array $data + */ + public function testImagePreprocessingWithoutValue($data) + { + $eavConfig = $this->getMock(\Magento\Eav\Model\Config::class, ['getEntityType'], [], '', false); + + $imageBackendModel = $this->objectManager->getObject( + \Magento\Catalog\Model\Category\Attribute\Backend\Image::class + ); + + $collection = new \Magento\Framework\DataObject(['attribute_collection' => [ + new \Magento\Framework\DataObject([ + 'attribute_code' => 'attribute1', + 'backend' => $imageBackendModel + ]), + new \Magento\Framework\DataObject([ + 'attribute_code' => 'attribute2', + 'backend' => new \Magento\Framework\DataObject() + ]) + ]]); + + $eavConfig->expects($this->once()) + ->method('getEntityType') + ->with(\Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE) + ->will($this->returnValue($collection)); + + $model = $this->objectManager->getObject(\Magento\Catalog\Controller\Adminhtml\Category\Save::class, [ + 'eavConfig' => $eavConfig + ]); + + $result = $model->imagePreprocessing($data); + + $this->assertEquals([ + 'attribute1' => false, + 'attribute2' => 123 + ], $result); + } + + public function testImagePreprocessingWithValue() + { + $eavConfig = $this->getMock(\Magento\Eav\Model\Config::class, ['getEntityType'], [], '', false); + + $imageBackendModel = $this->objectManager->getObject( + \Magento\Catalog\Model\Category\Attribute\Backend\Image::class + ); + + $collection = new \Magento\Framework\DataObject(['attribute_collection' => [ + new \Magento\Framework\DataObject([ + 'attribute_code' => 'attribute1', + 'backend' => $imageBackendModel + ]), + new \Magento\Framework\DataObject([ + 'attribute_code' => 'attribute2', + 'backend' => new \Magento\Framework\DataObject() + ]) + ]]); + + $eavConfig->expects($this->once()) + ->method('getEntityType') + ->with(\Magento\Catalog\Api\Data\CategoryAttributeInterface::ENTITY_TYPE_CODE) + ->will($this->returnValue($collection)); + + $model = $this->objectManager->getObject(Model::class, [ + 'eavConfig' => $eavConfig + ]); + + $result = $model->imagePreprocessing([ + 'attribute1' => 'somevalue', + 'attribute2' => null + ]); + + $this->assertEquals([ + 'attribute1' => 'somevalue', + 'attribute2' => null + ], $result); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AddAttributeToTemplateTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AddAttributeToTemplateTest.php new file mode 100644 index 0000000000000000000000000000000000000000..a55766f895b62511ced21eedaacc67f6c29811ae --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/AddAttributeToTemplateTest.php @@ -0,0 +1,247 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product; + +use Magento\Catalog\Controller\Adminhtml\Product\AddAttributeToTemplate; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Backend\App\Action\Context; +use Magento\Catalog\Controller\Adminhtml\Product\Builder as ProductBuilder; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\App\RequestInterface; +use Magento\Catalog\Api\AttributeSetRepositoryInterface; +use Magento\Eav\Api\Data\AttributeSetInterface; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Api\SearchCriteria; +use Magento\Eav\Api\AttributeGroupRepositoryInterface; +use Magento\Eav\Api\Data\AttributeGroupSearchResultsInterface; +use Magento\Eav\Api\Data\AttributeGroupInterfaceFactory; +use Magento\Eav\Api\Data\AttributeGroupInterface; +use Magento\Framework\Controller\Result\Json; + +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class AddAttributeToTemplateTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + /** + * @var AddAttributeToTemplate + */ + private $controller; + + /** + * @var Context|\PHPUnit_Framework_MockObject_MockObject + */ + private $contextMock; + + /** + * @var ProductBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $productBuilderMock; + + /** + * @var JsonFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $resultJsonFactoryMock; + + /** + * @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $requestMock; + + /** + * @var AttributeSetRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeSetRepositoryMock; + + /** + * @var AttributeSetInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeSetInterfaceMock; + + /** + * @var SearchCriteriaBuilder|\PHPUnit_Framework_MockObject_MockObject + */ + private $searchCriteriaBuilderMock; + + /** + * @var SearchCriteria|\PHPUnit_Framework_MockObject_MockObject + */ + private $searchCriteriaMock; + + /** + * @var AttributeGroupRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeGroupRepositoryMock; + + /** + * @var AttributeGroupSearchResultsInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeGroupSearchResultsMock; + + /** + * @var AttributeGroupInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeGroupInterfaceFactoryMock; + + /** + * @var AttributeGroupInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeGroupInterfaceMock; + + /** + * @var Json|\PHPUnit_Framework_MockObject_MockObject + */ + private $jsonMock; + + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + $this->contextMock = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->getMock(); + $this->productBuilderMock = $this->getMockBuilder(ProductBuilder::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resultJsonFactoryMock = $this->getMockBuilder(JsonFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + ->setMethods(['getParam', 'setParam']) + ->getMockForAbstractClass(); + $this->contextMock->expects($this->once()) + ->method('getRequest') + ->willReturn($this->requestMock); + $this->attributeSetRepositoryMock = $this->getMockBuilder(AttributeSetRepositoryInterface::class) + ->setMethods(['get']) + ->getMockForAbstractClass(); + $this->attributeSetInterfaceMock = $this->getMockBuilder(AttributeSetInterface::class) + ->getMockForAbstractClass(); + $this->searchCriteriaBuilderMock = $this->getMockBuilder(SearchCriteriaBuilder::class) + ->disableOriginalConstructor() + ->setMethods(['addFilter', 'create', 'setPageSize']) + ->getMockForAbstractClass(); + $this->searchCriteriaMock = $this->getMockBuilder(SearchCriteria::class) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeGroupRepositoryMock = $this->getMockBuilder(AttributeGroupRepositoryInterface::class) + ->setMethods(['getList']) + ->getMockForAbstractClass(); + $this->attributeGroupSearchResultsMock = $this->getMockBuilder(AttributeGroupSearchResultsInterface::class) + ->setMethods(['getItems']) + ->getMockForAbstractClass(); + $this->attributeGroupInterfaceFactoryMock = $this->getMockBuilder(AttributeGroupInterfaceFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $this->attributeGroupInterfaceMock = $this->getMockBuilder(AttributeGroupInterface::class) + ->setMethods(['getExtensionAttributes']) + ->getMockForAbstractClass(); + $this->jsonMock = $this->getMockBuilder(Json::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->controller = $this->objectManager->getObject( + AddAttributeToTemplate::class, + [ + 'context' => $this->contextMock, + 'productBuilder' => $this->productBuilderMock, + 'resultJsonFactory' => $this->resultJsonFactoryMock, + ] + ); + + $this->objectManager->setBackwardCompatibleProperty( + $this->controller, + 'attributeSetRepository', + $this->attributeSetRepositoryMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->controller, + 'searchCriteriaBuilder', + $this->searchCriteriaBuilderMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->controller, + 'attributeGroupRepository', + $this->attributeGroupRepositoryMock + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->controller, + 'attributeGroupFactory', + $this->attributeGroupInterfaceFactoryMock + ); + } + + public function testExecuteWithoutAttributeGroupItems() + { + $groupCode = 'attributes'; + $groupName = 'Attributes'; + $groupSortOrder = '15'; + $templateId = '4'; + $attributeIds = [ + 'selected' => ["178"], + 'total' => '1' + ]; + + $this->requestMock + ->expects($this->any()) + ->method('getParam') + ->willReturnMap( + [ + ['groupCode', null, $groupCode], + ['groupName', null, $groupName], + ['groupSortOrder', null, $groupSortOrder], + ['templateId', null, $templateId], + ['attributeIds', [], $attributeIds] + ] + ); + + $this->attributeSetRepositoryMock->expects($this->once()) + ->method('get') + ->willReturn($this->attributeSetInterfaceMock); + + $this->searchCriteriaBuilderMock->expects($this->any()) + ->method('addFilter') + ->willReturnSelf(); + $this->searchCriteriaBuilderMock->expects($this->any()) + ->method('create') + ->willReturn($this->searchCriteriaMock); + $this->searchCriteriaBuilderMock->expects($this->once()) + ->method('setPageSize') + ->willReturnSelf(); + $this->searchCriteriaBuilderMock->expects($this->never()) + ->method('addSortOrder') + ->willReturnSelf(); + + $this->attributeGroupRepositoryMock->expects($this->once()) + ->method('getList') + ->willReturn($this->attributeGroupSearchResultsMock); + $this->attributeGroupSearchResultsMock->expects($this->once()) + ->method('getItems') + ->willReturn(null); + + $this->attributeGroupInterfaceFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->attributeGroupInterfaceMock); + $this->attributeGroupInterfaceMock->expects($this->once()) + ->method('getExtensionAttributes') + ->willThrowException(new LocalizedException(__('Could not get extension attributes'))); + + $this->resultJsonFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($this->jsonMock); + $this->jsonMock->expects($this->once())->method('setJsonData') + ->willReturnSelf(); + + $this->controller->execute(); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php index a4218e92b3486db76afc61254553b914610d99c3..c67ed25d9c6665901beaeae6d7c2835b3bb20bc1 100644 --- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php @@ -5,18 +5,14 @@ */ namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Initialization; -use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper; use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\Product\Option; -use Magento\Catalog\Model\ProductRepository; use Magento\Framework\App\RequestInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; -use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Api\Data\WebsiteInterface; use Magento\Store\Model\StoreManagerInterface; -use Magento\Framework\Stdlib\DateTime\Filter\Date as DateFilter; use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory; use Magento\Catalog\Model\Product\Initialization\Helper\ProductLinks; @@ -34,11 +30,6 @@ class HelperTest extends \PHPUnit_Framework_TestCase */ protected $objectManager; - /** - * @var int - */ - protected $websiteId = 1; - /** * @var Helper */ @@ -64,106 +55,54 @@ class HelperTest extends \PHPUnit_Framework_TestCase */ protected $productMock; - /** - * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $storeMock; - - /** - * @var WebsiteInterface|\PHPUnit_Framework_MockObject_MockObject - */ - protected $websiteMock; - - /** - * @var DateFilter|\PHPUnit_Framework_MockObject_MockObject - */ - protected $dateFilterMock; - - /** - * @var ProductLinkInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject - */ - protected $productLinkFactoryMock; - - /** - * @var ProductRepository|\PHPUnit_Framework_MockObject_MockObject - */ - protected $productRepositoryMock; - /** * @var ProductCustomOptionInterfaceFactory|\PHPUnit_Framework_MockObject_MockObject */ protected $customOptionFactoryMock; /** - * @var Option|\PHPUnit_Framework_MockObject_MockObject - */ - protected $customOptionMock; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Catalog\Model\Product\Link\Resolver|\PHPUnit_Framework_MockObject_MockObject */ protected $linkResolverMock; /** - * @var ProductLinks + * @var ProductLinks|\PHPUnit_Framework_MockObject_MockObject */ protected $productLinksMock; protected function setUp() { $this->objectManager = new ObjectManager($this); - $this->productLinkFactoryMock = $this->getMockBuilder(ProductLinkInterfaceFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->productRepositoryMock = $this->getMockBuilder(ProductRepository::class) - ->disableOriginalConstructor() - ->getMock(); $this->requestMock = $this->getMockBuilder(RequestInterface::class) ->setMethods(['getPost']) ->getMockForAbstractClass(); - $this->storeMock = $this->getMockBuilder(StoreInterface::class) - ->setMethods(['getWebsite']) - ->getMockForAbstractClass(); - $this->websiteMock = $this->getMockBuilder(WebsiteInterface::class) - ->getMockForAbstractClass(); $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class) ->getMockForAbstractClass(); - $this->dateFilterMock = $this->getMockBuilder(DateFilter::class) - ->disableOriginalConstructor() - ->getMock(); $this->stockFilterMock = $this->getMockBuilder(StockDataFilter::class) ->disableOriginalConstructor() ->getMock(); $this->productMock = $this->getMockBuilder(Product::class) - ->setMethods([ - 'getId', - 'setWebsiteIds', - 'isLockedAttribute', - 'lockAttribute', - 'getAttributes', - 'unlockAttribute', - 'getOptionsReadOnly', - 'setCanSaveCustomOptions', - '__sleep', - '__wakeup', - 'getSku', - 'getProductLinks', - 'getWebsiteIds' - ]) + ->setMethods( + [ + 'getId', + 'isLockedAttribute', + 'lockAttribute', + 'getAttributes', + 'unlockAttribute', + 'getOptionsReadOnly', + 'getSku', + 'getProductLinks', + ] + ) ->disableOriginalConstructor() - ->getMock(); + ->getMockForAbstractClass(); $this->customOptionFactoryMock = $this->getMockBuilder(ProductCustomOptionInterfaceFactory::class) ->disableOriginalConstructor() ->setMethods(['create']) ->getMock(); - $this->customOptionMock = $this->getMockBuilder(Option::class) - ->disableOriginalConstructor() - ->setMethods(null) - ->getMock(); $this->productLinksMock = $this->getMockBuilder(ProductLinks::class) ->disableOriginalConstructor() ->getMock(); - $this->productLinksMock->expects($this->any()) ->method('initializeLinks') ->willReturn($this->productMock); @@ -173,10 +112,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase 'storeManager' => $this->storeManagerMock, 'stockFilter' => $this->stockFilterMock, 'productLinks' => $this->productLinksMock, - 'dateFilter' => $this->dateFilterMock, 'customOptionFactory' => $this->customOptionFactoryMock, - 'productLinkFactory' => $this->productLinkFactoryMock, - 'productRepository' => $this->productRepositoryMock, ]); $this->linkResolverMock = $this->getMockBuilder(\Magento\Catalog\Model\Product\Link\Resolver::class) @@ -190,9 +126,13 @@ class HelperTest extends \PHPUnit_Framework_TestCase /** * @covers \Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper::initialize - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @param bool $isSingleStore + * @param array $websiteIds + * @param array $expWebsiteIds + * + * @dataProvider initializeDataProvider */ - public function testInitialize() + public function testInitialize($isSingleStore, $websiteIds, $expWebsiteIds) { $optionsData = [ 'option1' => ['is_delete' => true, 'name' => 'name1', 'price' => 'price1', 'option_id' => ''], @@ -202,6 +142,7 @@ class HelperTest extends \PHPUnit_Framework_TestCase $productData = [ 'stock_data' => ['stock_data'], 'options' => $optionsData, + 'website_ids' => $websiteIds ]; $attributeNonDate = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) ->disableOriginalConstructor() @@ -218,69 +159,39 @@ class HelperTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $attributeNonDate->expects($this->any()) - ->method('getBackend') - ->willReturn($attributeNonDateBackEnd); - $attributeDate->expects($this->any()) - ->method('getBackend') - ->willReturn($attributeDateBackEnd); - $this->productMock->expects($this->any()) - ->method('getProductLinks') - ->willReturn([]); - $attributeNonDateBackEnd->expects($this->any()) - ->method('getType') - ->willReturn('non-datetime'); - $attributeDateBackEnd->expects($this->any()) - ->method('getType') - ->willReturn('datetime'); - - $attributesArray = [ - $attributeNonDate, - $attributeDate - ]; + $attributeNonDate->expects($this->any())->method('getBackend')->willReturn($attributeNonDateBackEnd); + $attributeDate->expects($this->any())->method('getBackend')->willReturn($attributeDateBackEnd); + $this->productMock->expects($this->any())->method('getProductLinks')->willReturn([]); + $attributeNonDateBackEnd->expects($this->any())->method('getType')->willReturn('non-datetime'); + $attributeDateBackEnd->expects($this->any())->method('getType')->willReturn('datetime'); $useDefaults = ['attributeCode1', 'attributeCode2']; - $this->requestMock->expects($this->at(0)) - ->method('getPost') - ->with('product') - ->willReturn($productData); - $this->requestMock->expects($this->at(1)) - ->method('getPost') - ->with('use_default') - ->willReturn($useDefaults); + $this->requestMock->expects($this->any())->method('getPost')->willReturnMap( + [ + ['product', [], $productData], + ['use_default', null, $useDefaults] + ] + ); $this->linkResolverMock->expects($this->once())->method('getLinks')->willReturn([]); - $this->stockFilterMock->expects($this->once()) - ->method('filter') - ->with(['stock_data']) + $this->stockFilterMock->expects($this->once())->method('filter')->with(['stock_data']) ->willReturn(['stock_data']); - $this->productMock->expects($this->once()) - ->method('isLockedAttribute') - ->with('media') - ->willReturn(true); - $this->productMock->expects($this->once()) - ->method('unlockAttribute') - ->with('media'); - $this->productMock->expects($this->any()) - ->method('getProductLinks') - ->willReturn([]); - $this->productMock->expects($this->once()) - ->method('lockAttribute') - ->with('media'); - $this->productMock->expects($this->once()) - ->method('getAttributes') - ->willReturn($attributesArray); - - $this->productMock->expects($this->any()) - ->method('getSku') - ->willReturn('sku'); - $this->productMock->expects($this->any()) - ->method('getOptionsReadOnly') - ->willReturn(false); - - $firstExpectedCustomOption = clone $this->customOptionMock; + $this->productMock->expects($this->once())->method('isLockedAttribute')->with('media')->willReturn(true); + $this->productMock->expects($this->once())->method('unlockAttribute')->with('media'); + $this->productMock->expects($this->any())->method('getProductLinks')->willReturn([]); + $this->productMock->expects($this->once())->method('lockAttribute')->with('media'); + $this->productMock->expects($this->once())->method('getAttributes') + ->willReturn([$attributeNonDate, $attributeDate]); + $this->productMock->expects($this->any())->method('getSku')->willReturn('sku'); + $this->productMock->expects($this->any())->method('getOptionsReadOnly')->willReturn(false); + + $customOptionMock = $this->getMockBuilder(Option::class) + ->disableOriginalConstructor() + ->setMethods(null) + ->getMock(); + $firstExpectedCustomOption = clone $customOptionMock; $firstExpectedCustomOption->setData($optionsData['option2']); - $secondExpectedCustomOption = clone $this->customOptionMock; + $secondExpectedCustomOption = clone $customOptionMock; $secondExpectedCustomOption->setData($optionsData['option3']); $this->customOptionFactoryMock->expects($this->any()) ->method('create') @@ -293,8 +204,13 @@ class HelperTest extends \PHPUnit_Framework_TestCase $secondExpectedCustomOption ] ]); + $website = $this->getMockBuilder(WebsiteInterface::class)->getMockForAbstractClass(); + $website->expects($this->any())->method('getId')->willReturn(1); + $this->storeManagerMock->expects($this->once())->method('isSingleStoreMode')->willReturn($isSingleStore); + $this->storeManagerMock->expects($this->any())->method('getWebsite')->willReturn($website); $this->assertEquals($this->productMock, $this->helper->initialize($this->productMock)); + $this->assertEquals($expWebsiteIds, $this->productMock->getDataByKey('website_ids')); $productOptions = $this->productMock->getOptions(); $this->assertTrue(2 == count($productOptions)); @@ -305,6 +221,35 @@ class HelperTest extends \PHPUnit_Framework_TestCase $this->assertTrue('sku' == $option2->getData('product_sku')); } + /** + * @return array + */ + public function initializeDataProvider() + { + return [ + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 1], + 'expected_website_ids' => ['1' => 1, '2' => 1] + ], + [ + 'single_store' => false, + 'website_ids' => ['1' => 1, '2' => 0], + 'expected_website_ids' => ['1' => 1] + ], + [ + 'single_store' => false, + 'website_ids' => ['1' => 0, '2' => 0], + 'expected_website_ids' => [] + ], + [ + 'single_store' => true, + 'website_ids' => [], + 'expected_website_ids' => ['1' => 1] + ], + ]; + } + /** * Data provider for testMergeProductOptions * diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..aa78fea89668773a2864741526d07c3e76aae1d1 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/Attribute/Backend/ImageTest.php @@ -0,0 +1,293 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\Category\Attribute\Backend; + +class ImageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Eav\Model\Entity\Attribute\AbstractAttribute + */ + private $attribute; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + private $objectManager; + + /** + * @var \Magento\Catalog\Model\ImageUploader + */ + private $imageUploader; + + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->attribute = $this->getMockForAbstractClass( + \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class, + [], + 'TestAttribute', + false, + false, + true, + ['getName'] + ); + + $this->attribute->expects($this->once()) + ->method('getName') + ->will($this->returnValue('test_attribute')); + + $this->logger = $this->getMockForAbstractClass( + \Psr\Log\LoggerInterface::class, + [], + 'TestLogger', + false, + false, + true, + ['critical'] + ); + + $this->imageUploader = $this->getMock( + \Magento\Catalog\Model\ImageUploader::class, + ['moveFileFromTmp'], + [], + '', + false + ); + } + + /** + * @return array + */ + public function deletedValueDataProvider() + { + return [ + [false], + [['delete' => true]] + ]; + } + + /** + * @dataProvider deletedValueDataProvider + * + * @param array $value + */ + public function testBeforeSaveValueDeletion($value) + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => $value + ]); + + $model->beforeSave($object); + + $this->assertEquals('', $object->getTestAttribute()); + } + + /** + * @return array + */ + public function invalidValueDataProvider() + { + $closure = function () { + return false; + }; + + return [ + [1234], + [true], + [new \stdClass()], + [$closure], + [['a' => 1, 'b' => 2]] + ]; + } + + /** + * @dataProvider invalidValueDataProvider + * + * @param array $value + */ + public function testBeforeSaveValueInvalid($value) + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => $value + ]); + + $model->beforeSave($object); + + $this->assertEquals('', $object->getTestAttribute()); + } + + public function testBeforeSaveAttributeFileName() + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => [ + ['name' => 'test123.jpg'] + ] + ]); + + $model->beforeSave($object); + + $this->assertEquals('test123.jpg', $object->getTestAttribute()); + } + + public function testBeforeSaveTemporaryAttribute() + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => [ + ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'] + ] + ]); + + $model->beforeSave($object); + + $this->assertEquals([ + ['name' => 'test123.jpg', 'tmp_name' => 'abc123', 'url' => 'http://www.example.com/test123.jpg'] + ], $object->getData('_additional_data_test_attribute')); + } + + public function testBeforeSaveAttributeStringValue() + { + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class); + $model->setAttribute($this->attribute); + + $object = new \Magento\Framework\DataObject([ + 'test_attribute' => 'test123.jpg' + ]); + + $model->beforeSave($object); + + $this->assertEquals('test123.jpg', $object->getTestAttribute()); + $this->assertNull($object->getData('_additional_data_test_attribute')); + } + + /** + * @return \Magento\Catalog\Model\Category\Attribute\Backend\Image + */ + private function setUpModelForAfterSave() + { + $objectManagerMock = $this->getMock( + \Magento\Framework\App\ObjectManager::class, + ['get'], + [], + '', + false + ); + + $imageUploaderMock = $this->imageUploader; + + $objectManagerMock->expects($this->any()) + ->method('get') + ->will($this->returnCallback(function ($class, $params = []) use ($imageUploaderMock) { + if ($class == \Magento\Catalog\CategoryImageUpload::class) { + return $imageUploaderMock; + } + + return $this->objectManager->get($class, $params); + })); + + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category\Attribute\Backend\Image::class, [ + 'objectManager' => $objectManagerMock, + 'logger' => $this->logger + ]); + $this->objectManager->setBackwardCompatibleProperty($model, 'imageUploader', $this->imageUploader); + + return $model->setAttribute($this->attribute); + } + + public function attributeValueDataProvider() + { + return [ + [[['name' => 'test1234.jpg']]], + ['test1234.jpg'], + [''], + [false] + ]; + } + + /** + * @dataProvider attributeValueDataProvider + * + * @param array $value + */ + public function testAfterSaveWithAdditionalData($value) + { + $model = $this->setUpModelForAfterSave(); + + $this->imageUploader->expects($this->once()) + ->method('moveFileFromTmp') + ->with($this->equalTo('test1234.jpg')); + + $object = new \Magento\Framework\DataObject( + [ + 'test_attribute' => $value, + '_additional_data_test_attribute' => [['name' => 'test1234.jpg']] + ] + ); + + $model->afterSave($object); + } + + /** + * @dataProvider attributeValueDataProvider + * + * @param array $value + */ + public function testAfterSaveWithoutAdditionalData($value) + { + $model = $this->setUpModelForAfterSave(); + + $this->imageUploader->expects($this->never()) + ->method('moveFileFromTmp'); + + $object = new \Magento\Framework\DataObject( + [ + 'test_attribute' => $value + ] + ); + + $model->afterSave($object); + } + + public function testAfterSaveWithExceptions() + { + $model = $this->setUpModelForAfterSave(); + + $exception = new \Exception(); + + $this->imageUploader->expects($this->any()) + ->method('moveFileFromTmp') + ->will($this->throwException($exception)); + + $this->logger->expects($this->once()) + ->method('critical') + ->with($this->equalTo($exception)); + + $object = new \Magento\Framework\DataObject( + [ + '_additional_data_test_attribute' => [['name' => 'test1234.jpg']] + ] + ); + + $model->afterSave($object); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php index 471037f1d3f450c1b2281e06f823222e7f7e28b6..37d0751a81bc06cfb4672874fff20dfefab6cb72 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryTest.php @@ -9,6 +9,7 @@ namespace Magento\Catalog\Test\Unit\Model; use Magento\Catalog\Model\Indexer; +use Magento\Catalog\Model\Category; /** * @SuppressWarnings(PHPMD.TooManyFields) @@ -16,113 +17,131 @@ use Magento\Catalog\Model\Indexer; */ class CategoryTest extends \PHPUnit_Framework_TestCase { - /** @var \Magento\Catalog\Model\Category */ - protected $category; - - /** @var \Magento\Framework\Model\Context|\PHPUnit_Framework_MockObject_MockObject */ - protected $context; - - /** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $eventManager; + /** + * @var \Magento\Catalog\Model\Category + */ + private $category; - /** @var \Magento\Framework\App\CacheInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $cacheManager; + /** + * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject + */ + private $registry; - /** @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject */ - protected $registry; + /** + * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeManager; - /** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $storeManager; + /** + * @var \Magento\Catalog\Model\ResourceModel\Category\Tree|\PHPUnit_Framework_MockObject_MockObject + */ + private $categoryTreeResource; - /** @var \Magento\Catalog\Model\ResourceModel\Category\Tree|\PHPUnit_Framework_MockObject_MockObject */ - protected $categoryTreeResource; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $categoryTreeFactory; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $categoryTreeFactory; + /** + * @var \Magento\Catalog\Api\CategoryRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $categoryRepository; - /** @var \Magento\Catalog\Api\CategoryRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $categoryRepository; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $storeCollectionFactory; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $storeCollectionFactory; + /** + * @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $url; - /** @var \Magento\Framework\UrlInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $url; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $productCollectionFactory; - /** @var \PHPUnit_Framework_MockObject_MockObject */ - protected $productCollectionFactory; + /** + * @var \Magento\Catalog\Model\Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $catalogConfig; - /** @var \Magento\Catalog\Model\Config|\PHPUnit_Framework_MockObject_MockObject */ - protected $catalogConfig; + /** + * @var \Magento\Framework\Filter\FilterManager|\PHPUnit_Framework_MockObject_MockObject + */ + private $filterManager; - /** @var \Magento\Framework\Filter\FilterManager|\PHPUnit_Framework_MockObject_MockObject */ - protected $filterManager; + /** + * @var \Magento\Catalog\Model\Indexer\Category\Flat\State|\PHPUnit_Framework_MockObject_MockObject + */ + private $flatState; - /** @var \Magento\Catalog\Model\Indexer\Category\Flat\State|\PHPUnit_Framework_MockObject_MockObject */ - protected $flatState; + /** + * @var \Magento\Framework\Indexer\IndexerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $flatIndexer; - /** @var \Magento\Framework\Indexer\IndexerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $flatIndexer; + /** + * @var \Magento\Framework\Indexer\IndexerInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $productIndexer; - /** @var \Magento\Framework\Indexer\IndexerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $productIndexer; + /** + * @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject + */ + private $categoryUrlPathGenerator; - /** @var \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator|\PHPUnit_Framework_MockObject_MockObject */ - protected $categoryUrlPathGenerator; + /** + * @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlFinder; - /** @var \Magento\UrlRewrite\Model\UrlFinderInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlFinder; + /** + * @var \Magento\Framework\Model\ResourceModel\AbstractResource|\PHPUnit_Framework_MockObject_MockObject + */ + private $resource; - /** @var \Magento\Framework\Model\ResourceModel\AbstractResource|\PHPUnit_Framework_MockObject_MockObject */ - protected $resource; + /** + * @var \Magento\Framework\Indexer\IndexerRegistry|\PHPUnit_Framework_MockObject_MockObject + */ + private $indexerRegistry; - /** @var \Magento\Framework\Indexer\IndexerRegistry|\PHPUnit_Framework_MockObject_MockObject */ - protected $indexerRegistry; + /** + * @var \Magento\Catalog\Api\CategoryAttributeRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataServiceMock; /** * @var \PHPUnit_Framework_MockObject_MockObject */ - protected $metadataServiceMock; + private $attributeValueFactory; /** - * @var \PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager */ - protected $attributeValueFactory; + private $objectManager; protected function setUp() { - $this->context = $this->getMock( - \Magento\Framework\Model\Context::class, - ['getEventDispatcher', 'getCacheManager'], + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->registry = $this->getMock(\Magento\Framework\Registry::class); + $this->storeManager = $this->getMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->categoryTreeResource = $this->getMock( + \Magento\Catalog\Model\ResourceModel\Category\Tree::class, + [], [], '', false ); - - $this->eventManager = $this->getMock(\Magento\Framework\Event\ManagerInterface::class); - $this->context->expects($this->any())->method('getEventDispatcher') - ->will($this->returnValue($this->eventManager)); - $this->cacheManager = $this->getMock(\Magento\Framework\App\CacheInterface::class); - $this->context->expects($this->any())->method('getCacheManager') - ->will($this->returnValue($this->cacheManager)); - - $this->registry = $this->getMock(\Magento\Framework\Registry::class); - $this->storeManager = $this->getMock(\Magento\Store\Model\StoreManagerInterface::class); - $this->categoryTreeResource = $this->getMock( - \Magento\Catalog\Model\ResourceModel\Category\Tree::class, - [], - [], - '', - false - ); - $this->categoryTreeFactory = $this->getMock( + $this->categoryTreeFactory = $this->getMock( \Magento\Catalog\Model\ResourceModel\Category\TreeFactory::class, ['create'], [], '', false); $this->categoryRepository = $this->getMock(\Magento\Catalog\Api\CategoryRepositoryInterface::class); - $this->storeCollectionFactory = $this->getMock( + $this->storeCollectionFactory = $this->getMock( \Magento\Store\Model\ResourceModel\Store\CollectionFactory::class, ['create'], [], @@ -130,7 +149,7 @@ class CategoryTest extends \PHPUnit_Framework_TestCase false ); $this->url = $this->getMock(\Magento\Framework\UrlInterface::class); - $this->productCollectionFactory = $this->getMock( + $this->productCollectionFactory = $this->getMock( \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory::class, ['create'], [], @@ -138,7 +157,7 @@ class CategoryTest extends \PHPUnit_Framework_TestCase false ); $this->catalogConfig = $this->getMock(\Magento\Catalog\Model\Config::class, [], [], '', false); - $this->filterManager = $this->getMock( + $this->filterManager = $this->getMock( \Magento\Framework\Filter\FilterManager::class, ['translitUrl'], [], @@ -148,7 +167,7 @@ class CategoryTest extends \PHPUnit_Framework_TestCase $this->flatState = $this->getMock(\Magento\Catalog\Model\Indexer\Category\Flat\State::class, [], [], '', false); $this->flatIndexer = $this->getMock(\Magento\Framework\Indexer\IndexerInterface::class); $this->productIndexer = $this->getMock(\Magento\Framework\Indexer\IndexerInterface::class); - $this->categoryUrlPathGenerator = $this->getMock( + $this->categoryUrlPathGenerator = $this->getMock( \Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator::class, [], [], @@ -156,19 +175,19 @@ class CategoryTest extends \PHPUnit_Framework_TestCase false ); $this->urlFinder = $this->getMock(\Magento\UrlRewrite\Model\UrlFinderInterface::class); - $this->resource = $this->getMock( + $this->resource = $this->getMock( \Magento\Catalog\Model\ResourceModel\Category::class, [], [], '', false ); - $this->indexerRegistry = $this->getMock( - \Magento\Framework\Indexer\IndexerRegistry::class, - ['get'], - [], - '', - false + $this->indexerRegistry = $this->getMock( + \Magento\Framework\Indexer\IndexerRegistry::class, + ['get'], + [], + '', + false ); $this->metadataServiceMock = $this->getMock(\Magento\Catalog\Api\CategoryAttributeRepositoryInterface::class); @@ -198,7 +217,7 @@ class CategoryTest extends \PHPUnit_Framework_TestCase public function testMoveWhenCannotFindParentCategory() { $this->markTestIncomplete('MAGETWO-31165'); - $parentCategory = $this->getMock( + $parentCategory = $this->getMock( \Magento\Catalog\Model\Category::class, ['getId', 'setStoreId', 'load'], [], @@ -223,7 +242,7 @@ class CategoryTest extends \PHPUnit_Framework_TestCase */ public function testMoveWhenCannotFindNewCategory() { - $parentCategory = $this->getMock( + $parentCategory = $this->getMock( \Magento\Catalog\Model\Category::class, ['getId', 'setStoreId', 'load'], [], @@ -250,7 +269,7 @@ class CategoryTest extends \PHPUnit_Framework_TestCase public function testMoveWhenParentCategoryIsSameAsChildCategory() { $this->markTestIncomplete('MAGETWO-31165'); - $parentCategory = $this->getMock( + $parentCategory = $this->getMock( \Magento\Catalog\Model\Category::class, ['getId', 'setStoreId', 'load'], [], @@ -277,7 +296,7 @@ class CategoryTest extends \PHPUnit_Framework_TestCase ->method('get') ->with('catalog_category_product') ->will($this->returnValue($indexer)); - $parentCategory = $this->getMock( + $parentCategory = $this->getMock( \Magento\Catalog\Model\Category::class, ['getId', 'setStoreId', 'load'], [], @@ -313,10 +332,9 @@ class CategoryTest extends \PHPUnit_Framework_TestCase protected function getCategoryModel() { - return (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject( + return $this->objectManager->getObject( \Magento\Catalog\Model\Category::class, [ - 'context' => $this->context, 'registry' => $this->registry, 'storeManager' => $this->storeManager, 'categoryTreeResource' => $this->categoryTreeResource, @@ -487,4 +505,76 @@ class CategoryTest extends \PHPUnit_Framework_TestCase $this->category->getCustomAttribute($descriptionAttributeCode)->getValue() ); } + + /** + * @return array + */ + public function getImageWithAttributeCodeDataProvider() + { + return [ + ['testimage', 'http://www.example.com/catalog/category/testimage'], + [false, false] + ]; + } + + /** + * @param string|bool $value + * @param string|bool $url + * + * @dataProvider getImageWithAttributeCodeDataProvider + */ + public function testGetImageWithAttributeCode($value, $url) + { + $storeManager = $this->getMock(\Magento\Store\Model\StoreManager::class, ['getStore'], [], '', false); + $store = $this->getMock(\Magento\Store\Model\Store::class, ['getBaseUrl'], [], '', false); + + $storeManager->expects($this->any()) + ->method('getStore') + ->will($this->returnValue($store)); + + $store->expects($this->any()) + ->method('getBaseUrl') + ->with(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) + ->will($this->returnValue('http://www.example.com/')); + + /** @var \Magento\Catalog\Model\Category $model */ + $model = $this->objectManager->getObject( + \Magento\Catalog\Model\Category::class, + [ + 'storeManager' => $storeManager + ] + ); + + $model->setData('attribute1', $value); + + $result = $model->getImageUrl('attribute1'); + + $this->assertEquals($url, $result); + } + + public function testGetImageWithoutAttributeCode() + { + $storeManager = $this->getMock(\Magento\Store\Model\StoreManager::class, ['getStore'], [], '', false); + $store = $this->getMock(\Magento\Store\Model\Store::class, ['getBaseUrl'], [], '', false); + + $storeManager->expects($this->any()) + ->method('getStore') + ->will($this->returnValue($store)); + + $store->expects($this->any()) + ->method('getBaseUrl') + ->with(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) + ->will($this->returnValue('http://www.example.com/')); + + /** @var \Magento\Catalog\Model\Category $model */ + $model = $this->objectManager->getObject(\Magento\Catalog\Model\Category::class, [ + 'storeManager' => $storeManager + ]); + + $model->setData('image', 'myimage'); + + $result = $model->getImageUrl(); + + $this->assertEquals('http://www.example.com/catalog/category/myimage', $result); + } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php index ec9cb624db79af510c2625ffee2680ede7653a19..32d62fd7a998e7bf820b7b0c26b6a7202350c26a 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/Backend/StockTest.php @@ -125,4 +125,17 @@ class StockTest extends \PHPUnit_Framework_TestCase $stockData = $object->getStockData(); $this->assertEquals(0, $stockData['qty']); } + + public function testBeforeSaveNoStockData() + { + $object = new \Magento\Framework\DataObject( + [ + self::ATTRIBUTE_NAME => ['is_in_stock' => 1, 'qty' => 0] + ] + ); + + $this->model->beforeSave($object); + $this->assertNull($object->getStockData()); + $this->assertNull($object->getData(self::ATTRIBUTE_NAME)); + } } 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 a032ffa33b37561117fac3f6e603cb77dd34152d..44f7f87cc2c62afc2c09d262bbd2721683fcfea3 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ImageTest.php @@ -180,7 +180,7 @@ class ImageTest extends \PHPUnit_Framework_TestCase $this->image->setBaseFile('/somefile.png'); $this->assertEquals('catalog/product/somefile.png', $this->image->getBaseFile()); $this->assertEquals( - 'catalog/product/cache/1//beff4985b56e3afdbeabfc89641a4582/somefile.png', + 'catalog/product/cache//beff4985b56e3afdbeabfc89641a4582/somefile.png', $this->image->getNewFile() ); } @@ -302,7 +302,7 @@ class ImageTest extends \PHPUnit_Framework_TestCase $this->testSetGetBaseFile(); $url = $this->image->getUrl(); $this->assertEquals( - 'http://magento.com/media/catalog/product/cache/1//beff4985b56e3afdbeabfc89641a4582/somefile.png', + 'http://magento.com/media/catalog/product/cache//beff4985b56e3afdbeabfc89641a4582/somefile.png', $url ); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php index e91bb469eb112d4de70cba92bff5b93ca069ca58..8c08d0c63bc5e93c7ab690f6441eaa02281b3255 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductRepositoryTest.php @@ -715,6 +715,7 @@ class ProductRepositoryTest extends \PHPUnit_Framework_TestCase ->method('process') ->with($searchCriteriaMock, $collectionMock); $collectionMock->expects($this->once())->method('load'); + $collectionMock->expects($this->once())->method('addCategoryIds'); $collectionMock->expects($this->once())->method('getItems')->willReturn([$itemsMock]); $collectionMock->expects($this->once())->method('getSize')->willReturn(128); $searchResultsMock = $this->getMock( diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php index 332e775b019b238b9ba1aec83f0f42302aafa68a..b97b7d2f9842355af1195b7b0ce7ed1d4aa7e4e8 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php @@ -30,6 +30,26 @@ class CollectionTest extends \PHPUnit_Framework_TestCase */ protected $collection; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $galleryResourceMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $entityMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPoolMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $galleryReadHandlerMock; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -100,24 +120,49 @@ class CollectionTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $entityMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class) + $this->entityMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class) ->disableOriginalConstructor() ->getMock(); + $this->galleryResourceMock = $this->getMockBuilder( + \Magento\Catalog\Model\ResourceModel\Product\Gallery::class + )->disableOriginalConstructor()->getMock(); + + $this->metadataPoolMock = $this->getMockBuilder( + \Magento\Framework\EntityManager\MetadataPool::class + )->disableOriginalConstructor()->getMock(); + + $this->galleryReadHandlerMock = $this->getMockBuilder( + \Magento\Catalog\Model\Product\Gallery\ReadHandler::class + )->disableOriginalConstructor()->getMock(); + $storeManager->expects($this->any())->method('getId')->willReturn(1); $storeManager->expects($this->any())->method('getStore')->willReturnSelf(); $universalFactory->expects($this->exactly(1))->method('create')->willReturnOnConsecutiveCalls( - $entityMock + $this->entityMock ); - $entityMock->expects($this->once())->method('getConnection')->willReturn($this->connectionMock); - $entityMock->expects($this->once())->method('getDefaultAttributes')->willReturn([]); - $entityMock->expects($this->any())->method('getTable')->willReturnArgument(0); + $this->entityMock->expects($this->once())->method('getConnection')->willReturn($this->connectionMock); + $this->entityMock->expects($this->once())->method('getDefaultAttributes')->willReturn([]); + $this->entityMock->expects($this->any())->method('getTable')->willReturnArgument(0); $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock); $helper = new ObjectManager($this); $this->prepareObjectManager([ - [\Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class, + [ + \Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class, $this->getMock(\Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class) + ], + [ + \Magento\Catalog\Model\ResourceModel\Product\Gallery::class, + $this->galleryResourceMock + ], + [ + \Magento\Framework\EntityManager\MetadataPool::class, + $this->metadataPoolMock + ], + [ + \Magento\Catalog\Model\Product\Gallery\ReadHandler::class, + $this->galleryReadHandlerMock ] ]); $this->collection = $helper->getObject( @@ -150,8 +195,8 @@ class CollectionTest extends \PHPUnit_Framework_TestCase public function testAddProductCategoriesFilter() { - $condition = ['in' => [1,2]]; - $values = [1,2]; + $condition = ['in' => [1, 2]]; + $values = [1, 2]; $conditionType = 'nin'; $preparedSql = "category_id IN(1,2)"; $tableName = "catalog_category_product"; @@ -174,6 +219,47 @@ class CollectionTest extends \PHPUnit_Framework_TestCase $this->collection->addCategoriesFilter([$conditionType => $values]); } + public function testAddMediaGalleryData() + { + $attributeId = 42; + $itemId = 4242; + $linkField = 'entity_id'; + $mediaGalleriesMock = [[$linkField => $itemId]]; + $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->getMock(); + $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->getMock(); + $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $metadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class) + ->disableOriginalConstructor() + ->getMock(); + $this->collection->addItem($itemMock); + $reflection = new \ReflectionClass(get_class($this->collection)); + $reflectionProperty = $reflection->getProperty('_isCollectionLoaded'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($this->collection, true); + + $this->galleryResourceMock->expects($this->once())->method('createBatchBaseSelect')->willReturn($selectMock); + $attributeMock->expects($this->once())->method('getAttributeId')->willReturn($attributeId); + $this->entityMock->expects($this->once())->method('getAttribute')->willReturn($attributeMock); + $itemMock->expects($this->atLeastOnce())->method('getId')->willReturn($itemId); + $selectMock->expects($this->once())->method('where')->with('entity.' . $linkField . ' IN (?)', [$itemId]); + $this->metadataPoolMock->expects($this->once())->method('getMetadata')->willReturn($metadataMock); + $metadataMock->expects($this->once())->method('getLinkField')->willReturn($linkField); + + $this->connectionMock->expects($this->once())->method('fetchAll')->with($selectMock)->willReturn( + [['entity_id' => $itemId]] + ); + $this->galleryReadHandlerMock->expects($this->once())->method('addMediaDataToProduct') + ->with($itemMock, $mediaGalleriesMock); + + $this->assertSame($this->collection, $this->collection->addMediaGalleryData()); + } + /** * @param $map */ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d296474c202f091b4c1435453ec7941007e10885 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CompositeBaseSelectProcessorTest.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product; + +use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\Catalog\Model\ResourceModel\Product\CompositeBaseSelectProcessor; +use Magento\Framework\DB\Select; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class CompositeBaseSelectProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManager + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = new ObjectManager($this); + } + + /** + * @expectedException \Magento\Framework\Exception\InputException + */ + public function testInitializeWithWrongProcessorInstance() + { + $processorValid = $this->getMock(BaseSelectProcessorInterface::class); + $processorInvalid = $this->getMock(\stdClass::class); + + $this->objectManager->getObject(CompositeBaseSelectProcessor::class, [ + 'baseSelectProcessors' => [$processorValid, $processorInvalid], + ]); + } + + public function testProcess() + { + $select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock(); + + $processorFirst = $this->getMock(BaseSelectProcessorInterface::class); + $processorFirst->expects($this->once())->method('process')->with($select)->willReturn($select); + + $processorSecond = $this->getMock(BaseSelectProcessorInterface::class); + $processorSecond->expects($this->once())->method('process')->with($select)->willReturn($select); + + /** @var CompositeBaseSelectProcessor $baseSelectProcessors */ + $baseSelectProcessors = $this->objectManager->getObject(CompositeBaseSelectProcessor::class, [ + 'baseSelectProcessors' => [$processorFirst, $processorSecond], + ]); + $this->assertEquals($select, $baseSelectProcessors->process($select)); + } +} diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0909f754a01c2c937e8c3692c52b1a256a27b0c3 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/StatusBaseSelectProcessorTest.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\Catalog\Model\ResourceModel\Product\StatusBaseSelectProcessor; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\Entity\Attribute\AttributeInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\EntityManager\EntityMetadataInterface; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class StatusBaseSelectProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $eavConfig; + + /** + * @var MetadataPool|\PHPUnit_Framework_MockObject_MockObject + */ + private $metadataPool; + + /** + * @var Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $select; + + /** + * @var StatusBaseSelectProcessor + */ + private $statusBaseSelectProcessor; + + protected function setUp() + { + $this->eavConfig = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock(); + $this->metadataPool = $this->getMockBuilder(MetadataPool::class)->disableOriginalConstructor()->getMock(); + $this->select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock(); + + $this->statusBaseSelectProcessor = (new ObjectManager($this))->getObject(StatusBaseSelectProcessor::class, [ + 'eavConfig' => $this->eavConfig, + 'metadataPool' => $this->metadataPool, + ]); + } + + public function testProcess() + { + $linkField = 'link_field'; + $backendTable = 'backend_table'; + $attributeId = 'attribute_id'; + + $metadata = $this->getMock(EntityMetadataInterface::class); + $metadata->expects($this->once()) + ->method('getLinkField') + ->willReturn($linkField); + $this->metadataPool->expects($this->once()) + ->method('getMetadata') + ->with(ProductInterface::class) + ->willReturn($metadata); + + $statusAttribute = $this->getMockBuilder(AttributeInterface::class) + ->setMethods(['getBackendTable', 'getAttributeId']) + ->getMock(); + $statusAttribute->expects($this->once()) + ->method('getBackendTable') + ->willReturn($backendTable); + $statusAttribute->expects($this->once()) + ->method('getAttributeId') + ->willReturn($attributeId); + $this->eavConfig->expects($this->once()) + ->method('getAttribute') + ->with(Product::ENTITY, ProductInterface::STATUS) + ->willReturn($statusAttribute); + + $this->select->expects($this->once()) + ->method('join') + ->with( + ['status_attr' => $backendTable], + sprintf('status_attr.%s = %s.%1$s', $linkField, BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), + [] + ) + ->willReturnSelf(); + $this->select->expects($this->at(1)) + ->method('where') + ->with('status_attr.attribute_id = ?', $attributeId) + ->willReturnSelf(); + $this->select->expects($this->at(2)) + ->method('where') + ->with('status_attr.value = ?', Status::STATUS_ENABLED) + ->willReturnSelf(); + + $this->assertEquals($this->select, $this->statusBaseSelectProcessor->process($this->select)); + } +} 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 1419c17e11b298d1fcdc554a3131d7b8c9b8ffe8..e1680ab5a4081946b73b375a85c0b53cf916afd4 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 @@ -754,7 +754,10 @@ class Eav extends AbstractModifier $meta['arguments']['data']['config']['wysiwyg'] = true; $meta['arguments']['data']['config']['wysiwygConfigData'] = [ 'add_variables' => false, - 'add_widgets' => false + 'add_widgets' => false, + 'add_directives' => true, + 'use_container' => true, + 'container_class' => 'hor-scroll', ]; return $meta; 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 0fbba264f7e93383f07434a7fe2ebf4586d54516..886c7a070adf93b68ec54f45813ba80911029e44 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 @@ -315,17 +315,6 @@ class General extends AbstractModifier $importsConfig = [ 'mask' => $this->locator->getStore()->getConfig('catalog/fields_masks/' . $listener), 'component' => 'Magento_Catalog/js/components/import-handler', - 'imports' => [ - 'handleNameChanges' => '${$.provider}:data.product.name', - 'handleDescriptionChanges' => '${$.provider}:data.product.description', - 'handleSkuChanges' => '${$.provider}:data.product.sku', - 'handleColorChanges' => '${$.provider}:data.product.color', - 'handleCountryChanges' => '${$.provider}:data.product.country_of_manufacture', - 'handleGenderChanges' => '${$.provider}:data.product.gender', - 'handleMaterialChanges' => '${$.provider}:data.product.material', - 'handleShortDescriptionChanges' => '${$.provider}:data.product.short_description', - 'handleSizeChanges' => '${$.provider}:data.product.size' - ], 'allowImport' => !$this->locator->getProduct()->getId(), ]; diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json index 64157f6bc8af74e82975668e43a62ef583227908..8c77dd77f98b74a53ee1124a16c473d843d21d60 100644 --- a/app/code/Magento/Catalog/composer.json +++ b/app/code/Magento/Catalog/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-eav": "100.2.*", "magento/module-cms": "101.1.*", diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml index d191f0332f5f28d28f22e981f8ebc74f0c0c8b98..6b39520ae021e9eb3bfafa20bda03e42c7db907a 100644 --- a/app/code/Magento/Catalog/etc/di.xml +++ b/app/code/Magento/Catalog/etc/di.xml @@ -837,4 +837,12 @@ <type name="Magento\Quote\Model\Quote\Item\ToOrderItem"> <plugin name="copy_quote_files_to_order" type="Magento\Catalog\Model\Plugin\QuoteItemProductOption"/> </type> + <preference for="Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface" type="Magento\Catalog\Model\ResourceModel\Product\CompositeBaseSelectProcessor" /> + <type name="Magento\Catalog\Model\ResourceModel\Product\CompositeBaseSelectProcessor"> + <arguments> + <argument name="baseSelectProcessors" xsi:type="array"> + <item name="status" xsi:type="object">Magento\Catalog\Model\ResourceModel\Product\StatusBaseSelectProcessor</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml index 1250b55b968480bf9c1d9f9952da6d864048d98f..0c9e6bb356fe14101d467a04aa91b23fb6873f09 100644 --- a/app/code/Magento/Catalog/etc/module.xml +++ b/app/code/Magento/Catalog/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_Catalog" setup_version="2.1.0"> + <module name="Magento_Catalog" setup_version="2.1.2"> <sequence> <module name="Magento_Eav"/> <module name="Magento_Cms"/> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/import-handler.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/import-handler.js index e67bde152475f62769d6eaff73548893f98ed7d0..a0aec918ccda24f1ae05486bc5d9ec59522f0ca7 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/components/import-handler.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/import-handler.js @@ -4,148 +4,78 @@ */ define([ + 'Magento_Ui/js/form/element/abstract', 'underscore', - 'Magento_Ui/js/form/element/textarea' -], function (_, Textarea) { + 'uiRegistry' +], function (Abstract, _, registry) { 'use strict'; - return Textarea.extend({ + return Abstract.extend({ defaults: { allowImport: true, autoImportIfEmpty: false, - values: { - 'name': '', - 'description': '', - 'sku': '', - 'color': '', - 'country_of_manufacture': '', - 'gender': '', - 'material': '', - 'short_description': '', - 'size': '' - }, - valueUpdate: 'input', - mask: '' + values: {}, + mask: '', + queryTemplate: 'ns = ${ $.ns }, index = ' }, - /** - * Handle name value changes, if it's allowed - * - * @param {String} newValue - */ - handleNameChanges: function (newValue) { - this.values.name = newValue; - this.updateValue(); - }, - - /** - * Handle description value changes, if it's allowed - * - * @param {String} newValue - */ - handleDescriptionChanges: function (newValue) { - this.values.description = newValue; - this.updateValue(); - }, + /** @inheritdoc */ + initialize: function () { + this._super(); - /** - * Handle sku value changes, if it's allowed - * - * @param {String} newValue - */ - handleSkuChanges: function (newValue) { - if (this.code !== 'sku') { - this.values.sku = newValue; - this.updateValue(); + if (this.allowImport) { + this.setHandlers(); } }, /** - * Handle color value changes, if it's allowed - * - * @param {String} newValue - */ - handleColorChanges: function (newValue) { - this.values.color = newValue; - this.updateValue(); - }, - - /** - * Handle country value changes, if it's allowed - * - * @param {String} newValue + * Split mask placeholder and attach events to placeholder fields. */ - handleCountryChanges: function (newValue) { - this.values.country = newValue; - this.updateValue(); - }, + setHandlers: function () { + var str = this.mask || '', + placeholders; - /** - * Handle gender value changes, if it's allowed - * - * @param {String} newValue - */ - handleGenderChanges: function (newValue) { - this.values.gender = newValue; - this.updateValue(); - }, + placeholders = str.match(/{{(.*?)}}/g); // Get placeholders - /** - * Handle material value changes, if it's allowed - * - * @param {String} newValue - */ - handleMaterialChanges: function (newValue) { - this.values.material = newValue; - this.updateValue(); - }, + _.each(placeholders, function (placeholder) { + placeholder = placeholder.replace(/[{{}}]/g, ''); // Remove curly braces - /** - * Handle short description value changes, if it's allowed - * - * @param {String} newValue - */ - handleShortDescriptionChanges: function (newValue) { - this.values['short_description'] = newValue; - this.updateValue(); + registry.get(this.queryTemplate + placeholder, function (component) { + this.values[placeholder] = component.getPreview(); + component.on('value', this.updateValue.bind(this, placeholder, component)); + component.valueUpdate = 'keyup'; + }.bind(this)); + }, this); }, /** - * Handle size value changes, if it's allowed + * Update field with mask value, if it's allowed. * - * @param {String} newValue + * @param {Object} placeholder + * @param {Object} component */ - handleSizeChanges: function (newValue) { - this.values.size = newValue; - this.updateValue(); - }, + updateValue: function (placeholder, component) { + var string = this.mask || '', + nonEmptyValueFlag = false; - /** - * Update field value, if it's allowed - */ - updateValue: function () { - var str = this.mask || '', - nonEmptyValueFlag = false, - tmpElement; + if (placeholder) { + this.values[placeholder] = component.getPreview() || ''; + } if (!this.allowImport) { return; } - if (str) { - _.each(this.values, function (propertyValue, propertyName) { - str = str.replace('{{' + propertyName + '}}', propertyValue); - nonEmptyValueFlag = nonEmptyValueFlag || !!propertyValue; - }); - } - - // strip tags - tmpElement = document.createElement('div'); - tmpElement.innerHTML = str; - str = tmpElement.textContent || tmpElement.innerText || ''; + _.each(this.values, function (propertyValue, propertyName) { + string = string.replace('{{' + propertyName + '}}', propertyValue); + nonEmptyValueFlag = nonEmptyValueFlag || !!propertyValue; + }); if (nonEmptyValueFlag) { - this.value(str); + string = string.replace(/(<([^>]+)>)/ig, ''); // Remove html tags + this.value(string); + } else { + this.value(''); } }, @@ -169,13 +99,20 @@ define([ * and disallow/allow import value */ userChanges: function () { + + /** + * As userChanges is called before updateValue, + * we forced to get value from component by reference + */ + var actualValue = arguments[1].currentTarget.value; + this._super(); - if (this.value() === '') { + if (actualValue === '') { this.allowImport = true; if (this.autoImportIfEmpty) { - this.updateValue(); + this.updateValue(null, null); } } else { this.allowImport = false; diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml index 4c8ae8eaae9524fe75f74ba5f0dd45ef045c2954..298cdcc29e953a74625a3834edab6b9a5eb47631 100644 --- a/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml +++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_category_view.xml @@ -28,53 +28,6 @@ </block> <block class="Magento\Catalog\Block\Product\ProductList\Toolbar" name="product_list_toolbar" template="Magento_Catalog::product/list/toolbar.phtml"> <block class="Magento\Theme\Block\Html\Pager" name="product_list_toolbar_pager"/> - <!-- The following code shows how to set your own pager increments --> - <!-- - <action method="setDefaultListPerPage"> - <argument name="limit" xsi:type="string">4</argument> - </action> - <action method="setDefaultGridPerPage"> - <argument name="limit" xsi:type="string">3</argument> - </action> - <action method="addPagerLimit"> - <argument name="mode" xsi:type="string">list</argument> - <argument name="limit" xsi:type="string">2</argument> - </action> - <action method="addPagerLimit"> - <argument name="mode" xsi:type="string">list</argument> - <argument name="limit" xsi:type="string">4</argument> - </action> - <action method="addPagerLimit"> - <argument name="mode" xsi:type="string">list</argument> - <argument name="limit" xsi:type="string">6</argument> - </action> - <action method="addPagerLimit"> - <argument name="mode" xsi:type="string">list</argument> - <argument name="limit" xsi:type="string">8</argument> - </action> - <action method="addPagerLimit" translate="label"> - <argument name="mode" xsi:type="string">list</argument> - <argument name="limit" xsi:type="string">all</argument> - <argument name="label" xsi:type="string">All</argument> - </action> - <action method="addPagerLimit"> - <argument name="mode" xsi:type="string">grid</argument> - <argument name="limit" xsi:type="string">3</argument> - </action> - <action method="addPagerLimit"> - <argument name="mode" xsi:type="string">grid</argument> - <argument name="limit" xsi:type="string">6</argument> - </action> - <action method="addPagerLimit"> - <argument name="mode" xsi:type="string">grid</argument> - <argument name="limit" xsi:type="string">9</argument> - </action> - <action method="addPagerLimit" translate="label"> - <argument name="mode" xsi:type="string">grid</argument> - <argument name="limit" xsi:type="string">all</argument> - <argument name="label" xsi:type="string">All</argument> - </action> - --> </block> <action method="setToolbarBlockName"> <argument name="name" xsi:type="string">product_list_toolbar</argument> diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php index c9d155177e48a588a655d603234482ac53cbe545..fa704272962162d43edd1d129c1229275614015a 100644 --- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php @@ -5,7 +5,6 @@ */ namespace Magento\CatalogImportExport\Model\Export; -use Magento\Framework\DB\Ddl\Table; use Magento\ImportExport\Model\Import; use \Magento\Store\Model\Store; use \Magento\CatalogImportExport\Model\Import\Product as ImportProduct; @@ -850,6 +849,24 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity return $writer->getContents(); } + /** + * {@inheritdoc} + */ + protected function _prepareEntityCollection(\Magento\Eav\Model\Entity\Collection\AbstractCollection $collection) + { + $exportFilter = !empty($this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP]) ? + $this->_parameters[\Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP] : []; + + if (isset($exportFilter['category_ids']) + && trim($exportFilter['category_ids']) + && $collection instanceof \Magento\Catalog\Model\ResourceModel\Product\Collection + ) { + $collection->addCategoriesFilter(['in' => explode(',', $exportFilter['category_ids'])]); + } + + return parent::_prepareEntityCollection($collection); + } + /** * Get export data for collection * diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php index afb2e86b6248a79523405c78acd1cf27953b90c2..54208dcdba534139c96e8fd975e0757f9ecb11e1 100644 --- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php +++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php @@ -2304,7 +2304,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $sku = $rowData[self::COL_SKU]; - if (isset($this->_oldSku[$sku])) { + if (isset($this->_oldSku[$sku]) && Import::BEHAVIOR_REPLACE !== $this->getBehavior()) { // can we get all necessary data from existent DB product? // check for supported type of existing product if (isset($this->_productTypeModels[$this->_oldSku[$sku]['type_id']])) { @@ -2356,7 +2356,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity $rowAttributesValid = $productTypeValidator->isRowValid( $rowData, $rowNum, - !isset($this->_oldSku[$sku]) + !(isset($this->_oldSku[$sku]) && Import::BEHAVIOR_REPLACE !== $this->getBehavior()) ); if (!$rowAttributesValid && self::SCOPE_DEFAULT == $rowScope) { // mark SCOPE_DEFAULT row as invalid for future child rows if product not in DB already diff --git a/app/code/Magento/CatalogImportExport/composer.json b/app/code/Magento/CatalogImportExport/composer.json index 7dbd7f4bc5e656ff09712549886e97a95fa3f4f1..8ed5f43692df51482038e1d1f7d4b91f05fd8c23 100644 --- a/app/code/Magento/CatalogImportExport/composer.json +++ b/app/code/Magento/CatalogImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-catalog-url-rewrite": "100.2.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php new file mode 100644 index 0000000000000000000000000000000000000000..829fa8decda7d74b554810ba17118faba2ca970c --- /dev/null +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php @@ -0,0 +1,51 @@ +<?php +/** + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogInventory\Model\ResourceModel\Product; + +use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\CatalogInventory\Model\Stock; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; + +/** + * Class StockStatusBaseSelectProcessor + */ +class StockStatusBaseSelectProcessor implements BaseSelectProcessorInterface +{ + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @param ResourceConnection $resource + */ + public function __construct(ResourceConnection $resource) + { + $this->resource = $resource; + } + + /** + * Add stock item filter to selects + * + * @param Select $select + * @return Select + */ + public function process(Select $select) + { + $stockStatusTable = $this->resource->getTableName('cataloginventory_stock_status'); + + /** @var Select $select */ + $select->join( + ['stock' => $stockStatusTable], + sprintf('stock.product_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), + [] + ) + ->where('stock.stock_status = ?', Stock::STOCK_IN_STOCK); + return $select; + } +} diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4756e42ffe602bfc3b258656e0f2abe490a3acc1 --- /dev/null +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogInventory\Test\Unit\Model\ResourceModel\Product; + +use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\CatalogInventory\Model\ResourceModel\Product\StockStatusBaseSelectProcessor; +use Magento\CatalogInventory\Model\Stock; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Select; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class StockStatusBaseSelectProcessorTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ResourceConnection|\PHPUnit_Framework_MockObject_MockObject + */ + private $resource; + + /** + * @var Select|\PHPUnit_Framework_MockObject_MockObject + */ + private $select; + + /** + * @var StockStatusBaseSelectProcessor + */ + private $stockStatusBaseSelectProcessor; + + protected function setUp() + { + $this->resource = $this->getMockBuilder(ResourceConnection::class)->disableOriginalConstructor()->getMock(); + $this->select = $this->getMockBuilder(Select::class)->disableOriginalConstructor()->getMock(); + + $this->stockStatusBaseSelectProcessor = (new ObjectManager($this))->getObject( + StockStatusBaseSelectProcessor::class, + [ + 'resource' => $this->resource, + ] + ); + } + + public function testProcess() + { + $tableName = 'table_name'; + + $this->resource->expects($this->once()) + ->method('getTableName') + ->with('cataloginventory_stock_status') + ->willReturn($tableName); + + $this->select->expects($this->once()) + ->method('join') + ->with( + ['stock' => $tableName], + sprintf('stock.product_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), + [] + ) + ->willReturnSelf(); + $this->select->expects($this->once()) + ->method('where') + ->with('stock.stock_status = ?', Stock::STOCK_IN_STOCK) + ->willReturnSelf(); + + $this->stockStatusBaseSelectProcessor->process($this->select); + } +} diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json index 254d8d2db4bf9db35b338507cdd744d1aaf0e152..684f616ecd6fcfca7088c257afec000fba6a4b1d 100644 --- a/app/code/Magento/CatalogInventory/composer.json +++ b/app/code/Magento/CatalogInventory/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-inventory", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml index 71b42ef89f73ce981f25393179fbbd05f59a4d08..e3ca5c01cedab6b5041a16248308208b3cccbcf5 100644 --- a/app/code/Magento/CatalogInventory/etc/di.xml +++ b/app/code/Magento/CatalogInventory/etc/di.xml @@ -79,4 +79,11 @@ <argument name="indexerProcessor" xsi:type="object">Magento\CatalogInventory\Model\Indexer\Stock\Processor</argument> </arguments> </type> + <type name="Magento\Catalog\Model\ResourceModel\Product\CompositeBaseSelectProcessor"> + <arguments> + <argument name="baseSelectProcessors" xsi:type="array"> + <item name="stock_status" xsi:type="object">Magento\CatalogInventory\Model\ResourceModel\Product\StockStatusBaseSelectProcessor</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php index 7d8d44dcbb68ad2539f0556fb7aec8eebe0caa2e..55b76eb225028a1a3d96d115066a4abc4b78fd60 100644 --- a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php +++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php @@ -6,7 +6,8 @@ namespace Magento\CatalogRule\Model\ResourceModel\Product; use Magento\Catalog\Api\Data\ProductInterface; -use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\DB\Select; use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; @@ -42,6 +43,11 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec */ private $metadataPool; + /** + * @var BaseSelectProcessorInterface + */ + private $baseSelectProcessor; + /** * @param \Magento\Store\Model\StoreManagerInterface $storeManager * @param \Magento\Framework\App\ResourceConnection $resourceConnection @@ -49,6 +55,7 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec * @param \Magento\Framework\Stdlib\DateTime $dateTime * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param BaseSelectProcessorInterface $baseSelectProcessor */ public function __construct( \Magento\Store\Model\StoreManagerInterface $storeManager, @@ -56,7 +63,8 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec \Magento\Customer\Model\Session $customerSession, \Magento\Framework\Stdlib\DateTime $dateTime, \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, - \Magento\Framework\EntityManager\MetadataPool $metadataPool + \Magento\Framework\EntityManager\MetadataPool $metadataPool, + BaseSelectProcessorInterface $baseSelectProcessor = null ) { $this->storeManager = $storeManager; $this->resource = $resourceConnection; @@ -64,6 +72,8 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec $this->dateTime = $dateTime; $this->localeDate = $localeDate; $this->metadataPool = $metadataPool; + $this->baseSelectProcessor = (null !== $baseSelectProcessor) + ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class); } /** @@ -76,25 +86,28 @@ class LinkedProductSelectBuilderByCatalogRulePrice implements LinkedProductSelec $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField(); $productTable = $this->resource->getTableName('catalog_product_entity'); - return [$this->resource->getConnection()->select() - ->from(['parent' => $productTable], '') - ->joinInner( - ['link' => $this->resource->getTableName('catalog_product_relation')], - "link.parent_id = parent.$linkField", - [] - )->joinInner( - ['child' => $productTable], - "child.entity_id = link.child_id", - ['entity_id'] - )->joinInner( - ['t' => $this->resource->getTableName('catalogrule_product_price')], - 't.product_id = child.entity_id', - [] - )->where('parent.entity_id = ? ', $productId) + $priceSelect = $this->resource->getConnection()->select() + ->from(['parent' => $productTable], '') + ->joinInner( + ['link' => $this->resource->getTableName('catalog_product_relation')], + "link.parent_id = parent.$linkField", + [] + )->joinInner( + [BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable], + sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS), + ['entity_id'] + )->joinInner( + ['t' => $this->resource->getTableName('catalogrule_product_price')], + sprintf('t.product_id = %s.%s', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS, $linkField), + [] + )->where('parent.entity_id = ?', $productId) ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId()) ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId()) ->where('t.rule_date = ?', $currentDate) ->order('t.rule_price ' . Select::SQL_ASC) - ->limit(1)]; + ->limit(1); + $priceSelect = $this->baseSelectProcessor->process($priceSelect); + + return [$priceSelect]; } } diff --git a/app/code/Magento/CatalogRule/Setup/InstallSchema.php b/app/code/Magento/CatalogRule/Setup/InstallSchema.php index 70c5724d446de5e3cd151e69b9ffc5534fec1cbe..cb36f6efbdcde67cff29f360d2aef0d65a954e6a 100644 --- a/app/code/Magento/CatalogRule/Setup/InstallSchema.php +++ b/app/code/Magento/CatalogRule/Setup/InstallSchema.php @@ -25,6 +25,9 @@ class InstallSchema implements InstallSchemaInterface $installer->startSetup(); + $customerGroupTable = $setup->getConnection()->describeTable($setup->getTable('customer_group')); + $customerGroupIdType = $customerGroupTable['customer_group_id']['DATA_TYPE'] == 'int' + ? \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER : $customerGroupTable['customer_group_id']['DATA_TYPE']; /** * Create table 'catalogrule' */ @@ -372,7 +375,7 @@ class InstallSchema implements InstallSchemaInterface ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true, 'default' => '0'], 'Customer Group Id' @@ -477,7 +480,7 @@ class InstallSchema implements InstallSchemaInterface ) ->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Customer Group Id' diff --git a/app/code/Magento/CatalogRule/Setup/UpgradeSchema.php b/app/code/Magento/CatalogRule/Setup/UpgradeSchema.php index 1d576e74c4c80caabcac9a53f7d3701ba5d66ded..6bfb927c9c8f5ce33d53e68853554d131e5f188f 100644 --- a/app/code/Magento/CatalogRule/Setup/UpgradeSchema.php +++ b/app/code/Magento/CatalogRule/Setup/UpgradeSchema.php @@ -26,6 +26,20 @@ class UpgradeSchema implements UpgradeSchemaInterface $this->removeSubProductDiscounts($setup); } + if (version_compare($context->getVersion(), '2.0.2', '<')) { + $tables = [ + 'catalogrule_product', + 'catalogrule_product_price', + ]; + foreach ($tables as $table) { + $setup->getConnection()->modifyColumn( + $setup->getTable($table), + 'customer_group_id', + ['type' => 'integer'] + ); + } + } + $setup->endSetup(); } diff --git a/app/code/Magento/CatalogRule/composer.json b/app/code/Magento/CatalogRule/composer.json index 71eb5b49db1fc68eadb064968796cfdaf1081d60..004ab82a028d3aba288b71a234dead24d63a4755 100644 --- a/app/code/Magento/CatalogRule/composer.json +++ b/app/code/Magento/CatalogRule/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-rule", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-rule": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/CatalogRule/etc/module.xml b/app/code/Magento/CatalogRule/etc/module.xml index ea6a730279ed54ba2caf76f1cab17d8fc21f9585..d7db233e1eb06ee815e1c7e71a325b0c0d0f8f92 100644 --- a/app/code/Magento/CatalogRule/etc/module.xml +++ b/app/code/Magento/CatalogRule/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_CatalogRule" setup_version="2.0.1"> + <module name="Magento_CatalogRule" setup_version="2.0.2"> <sequence> <module name="Magento_Rule"/> <module name="Magento_Catalog"/> diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json index cc51269e2d9720e380612cf56c712ff2459bbe35..b930380f7bb025ea409be398c013c12773cb2f20 100644 --- a/app/code/Magento/CatalogRuleConfigurable/composer.json +++ b/app/code/Magento/CatalogRuleConfigurable/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-rule-configurable", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-configurable-product": "100.2.*", "magento/framework": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php index 9b75e6e6e0c325819d09f543cc445ae61eb7fb84..ddf86951068acc3c95017e54e489faf92e4b28cc 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider.php @@ -6,6 +6,7 @@ namespace Magento\CatalogSearch\Model\Adapter\Mysql\Aggregation; use Magento\Catalog\Model\Product; +use Magento\CatalogInventory\Model\Stock; use Magento\Customer\Model\Session; use Magento\Eav\Model\Config; use Magento\Framework\App\ResourceConnection; @@ -79,7 +80,13 @@ class DataProvider implements DataProviderInterface $select = $this->getSelect(); - if ($attribute->getAttributeCode() == 'price') { + $select->joinInner( + ['entities' => $entityIdsTable->getName()], + 'main_table.entity_id = entities.entity_id', + [] + ); + + if ($attribute->getAttributeCode() === 'price') { /** @var \Magento\Store\Model\Store $store */ $store = $this->scopeResolver->getScope($currentScope); if (!$store instanceof \Magento\Store\Model\Store) { @@ -94,19 +101,24 @@ class DataProvider implements DataProviderInterface $currentScopeId = $this->scopeResolver->getScope($currentScope) ->getId(); $table = $this->resource->getTableName( - 'catalog_product_index_eav' . ($attribute->getBackendType() == 'decimal' ? '_decimal' : '') + 'catalog_product_index_eav' . ($attribute->getBackendType() === 'decimal' ? '_decimal' : '') ); - $select->from(['main_table' => $table], ['value']) + $subSelect = $select; + $subSelect->from(['main_table' => $table], ['main_table.value']) + ->joinLeft( + ['stock_index' => $this->resource->getTableName('cataloginventory_stock_status')], + 'main_table.source_id = stock_index.product_id', + [] + ) ->where('main_table.attribute_id = ?', $attribute->getAttributeId()) - ->where('main_table.store_id = ? ', $currentScopeId); + ->where('main_table.store_id = ? ', $currentScopeId) + ->where('stock_index.stock_status = ?', Stock::STOCK_IN_STOCK) + ->group(['main_table.entity_id', 'main_table.value']); + $parentSelect = $this->getSelect(); + $parentSelect->from(['main_table' => $subSelect], ['main_table.value']); + $select = $parentSelect; } - $select->joinInner( - ['entities' => $entityIdsTable->getName()], - 'main_table.entity_id = entities.entity_id', - [] - ); - return $select; } diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php new file mode 100644 index 0000000000000000000000000000000000000000..7099ce2502b19e21de48ae570e2dc063be6162d1 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php @@ -0,0 +1,44 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Model\Adapter\Mysql\Filter; + + +use Magento\CatalogSearch\Model\Search\RequestGenerator; + +/** + * Purpose of class is to resolve table alias for Search Request filter + */ +class AliasResolver +{ + /** + * The suffix for stock status filter that may be added to the query beside the filter query + * Used when showing of Out of Stock products is disabled. + */ + const STOCK_FILTER_SUFFIX = '_stock'; + + /** + * @param \Magento\Framework\Search\Request\FilterInterface $filter + * @return string alias of the filter in database + */ + public function getAlias(\Magento\Framework\Search\Request\FilterInterface $filter) + { + $alias = null; + $field = $filter->getField(); + switch ($field) { + case 'price': + $alias = 'price_index'; + break; + case 'category_ids': + $alias = 'category_ids_index'; + break; + default: + $alias = $field . RequestGenerator::FILTER_SUFFIX; + break; + } + return $alias; + } +} diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php index 05205f04f8b99cd903f345f8c1f7dc04384e7aee..fb579c1dce29c7da26b35288ff61040363f4f510 100644 --- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php +++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/Preprocessor.php @@ -8,8 +8,11 @@ namespace Magento\CatalogSearch\Model\Adapter\Mysql\Filter; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\CatalogInventory\Model\Stock; use Magento\CatalogSearch\Model\Search\TableMapper; use Magento\Eav\Model\Config; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; use Magento\Framework\App\ScopeResolverInterface; use Magento\Framework\DB\Adapter\AdapterInterface; @@ -17,6 +20,7 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Search\Adapter\Mysql\ConditionManager; use Magento\Framework\Search\Adapter\Mysql\Filter\PreprocessorInterface; use Magento\Framework\Search\Request\FilterInterface; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; /** @@ -60,9 +64,14 @@ class Preprocessor implements PreprocessorInterface private $metadataPool; /** - * @var TableMapper + * @var ScopeConfigInterface */ - private $tableMapper; + private $scopeConfig; + + /** + * @var AliasResolver + */ + private $aliasResolver; /** * @param ConditionManager $conditionManager @@ -71,6 +80,9 @@ class Preprocessor implements PreprocessorInterface * @param ResourceConnection $resource * @param TableMapper $tableMapper * @param string $attributePrefix + * @param ScopeConfigInterface $scopeConfig + * @param AliasResolver $aliasResolver + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( ConditionManager $conditionManager, @@ -78,7 +90,9 @@ class Preprocessor implements PreprocessorInterface Config $config, ResourceConnection $resource, TableMapper $tableMapper, - $attributePrefix + $attributePrefix, + ScopeConfigInterface $scopeConfig = null, + AliasResolver $aliasResolver = null ) { $this->conditionManager = $conditionManager; $this->scopeResolver = $scopeResolver; @@ -86,7 +100,16 @@ class Preprocessor implements PreprocessorInterface $this->resource = $resource; $this->connection = $resource->getConnection(); $this->attributePrefix = $attributePrefix; - $this->tableMapper = $tableMapper; + + if (null === $scopeConfig) { + $scopeConfig = ObjectManager::getInstance()->get(ScopeConfigInterface::class); + } + if (null === $aliasResolver) { + $aliasResolver = ObjectManager::getInstance()->get(AliasResolver::class); + } + + $this->scopeConfig = $scopeConfig; + $this->aliasResolver = $aliasResolver; } /** @@ -117,7 +140,7 @@ class Preprocessor implements PreprocessorInterface } elseif ($filter->getField() === 'category_ids') { return 'category_ids_index.category_id = ' . (int) $filter->getValue(); } elseif ($attribute->isStatic()) { - $alias = $this->tableMapper->getMappingAlias($filter); + $alias = $this->aliasResolver->getAlias($filter); $resultQuery = str_replace( $this->connection->quoteIdentifier($attribute->getAttributeCode()), $this->connection->quoteIdentifier($alias . '.' . $attribute->getAttributeCode()), @@ -208,7 +231,7 @@ class Preprocessor implements PreprocessorInterface */ private function processTermSelect(FilterInterface $filter, $isNegation) { - $alias = $this->tableMapper->getMappingAlias($filter); + $alias = $this->aliasResolver->getAlias($filter); if (is_array($filter->getValue())) { $value = sprintf( '%s IN (%s)', @@ -224,9 +247,31 @@ class Preprocessor implements PreprocessorInterface $value ); + if ($this->isAddStockFilter()) { + $resultQuery = sprintf( + '%1$s AND %2$s%3$s.stock_status = %4$s', + $resultQuery, + $alias, + AliasResolver::STOCK_FILTER_SUFFIX, + Stock::STOCK_IN_STOCK + ); + } + return $resultQuery; } + /** + * @return bool + */ + private function isAddStockFilter() + { + $isShowOutOfStock = $this->scopeConfig->isSetFlag( + 'cataloginventory/options/show_out_of_stock', + ScopeInterface::SCOPE_STORE + ); + return false === $isShowOutOfStock; + } + /** * Get product metadata pool * diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php new file mode 100644 index 0000000000000000000000000000000000000000..8626b2ea15b077fc42380afaba82b245e7387937 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; + +/** + * Strategy which processes exclusions from general rules + */ +class ExclusionStrategy implements FilterStrategyInterface +{ + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resourceConnection; + + /** + * @var AliasResolver + */ + private $aliasResolver; + + /** + * @var \Magento\Store\Model\StoreManagerInterface + */ + private $storeManager; + + /** + * @param \Magento\Framework\App\ResourceConnection $resourceConnection + * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param AliasResolver $aliasResolver + */ + public function __construct( + \Magento\Framework\App\ResourceConnection $resourceConnection, + \Magento\Store\Model\StoreManagerInterface $storeManager, + AliasResolver $aliasResolver + ) { + $this->resourceConnection = $resourceConnection; + $this->storeManager = $storeManager; + $this->aliasResolver = $aliasResolver; + } + + /** + * {@inheritDoc} + */ + public function apply( + \Magento\Framework\Search\Request\FilterInterface $filter, + \Magento\Framework\DB\Select $select + ) { + $isApplied = false; + $field = $filter->getField(); + if ('price' === $field) { + $alias = $this->aliasResolver->getAlias($filter); + $tableName = $this->resourceConnection->getTableName('catalog_product_index_price'); + $select->joinInner( + [ + $alias => $tableName + ], + $this->resourceConnection->getConnection()->quoteInto( + 'search_index.entity_id = price_index.entity_id AND price_index.website_id = ?', + $this->storeManager->getWebsite()->getId() + ), + [] + ); + $isApplied = true; + } elseif ('category_ids' === $field) { + $alias = $this->aliasResolver->getAlias($filter); + $tableName = $this->resourceConnection->getTableName('catalog_category_product_index'); + $select->joinInner( + [ + $alias => $tableName + ], + 'search_index.entity_id = category_ids_index.product_id', + [] + ); + $isApplied = true; + } + return $isApplied; + } +} diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterContext.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterContext.php new file mode 100644 index 0000000000000000000000000000000000000000..d244e3d5f754893fb671a3b6c81565eb5c920ff7 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterContext.php @@ -0,0 +1,100 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + + +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\Eav\Model\Config as EavConfig; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; + +/** + * FilterContext represents a Context of the Strategy pattern + * Its responsibility is to choose appropriate strategy to apply passed filter to the Select + */ +class FilterContext implements FilterStrategyInterface +{ + /** + * @var ExclusionStrategy + */ + private $exclusionStrategy; + + /** + * @var EavConfig + */ + private $eavConfig; + + /** + * @var TermDropdownStrategy + */ + private $termDropdownStrategy; + + /** + * @var StaticAttributeStrategy + */ + private $staticAttributeStrategy; + + /** + * @var AliasResolver + */ + private $aliasResolver; + + /** + * @param EavConfig $eavConfig + * @param AliasResolver $aliasResolver + * @param ExclusionStrategy $exclusionStrategy + * @param TermDropdownStrategy $termDropdownStrategy + * @param StaticAttributeStrategy $staticAttributeStrategy + */ + public function __construct( + EavConfig $eavConfig, + AliasResolver $aliasResolver, + ExclusionStrategy $exclusionStrategy, + TermDropdownStrategy $termDropdownStrategy, + StaticAttributeStrategy $staticAttributeStrategy + ) { + $this->eavConfig = $eavConfig; + $this->aliasResolver = $aliasResolver; + $this->exclusionStrategy = $exclusionStrategy; + $this->termDropdownStrategy = $termDropdownStrategy; + $this->staticAttributeStrategy = $staticAttributeStrategy; + } + + /** + * {@inheritDoc} + */ + public function apply( + \Magento\Framework\Search\Request\FilterInterface $filter, + \Magento\Framework\DB\Select $select + ) { + $isApplied = $this->exclusionStrategy->apply($filter, $select); + + if (!$isApplied) { + $attribute = $this->getAttributeByCode($filter->getField()); + if ($attribute) { + if ($filter->getType() === \Magento\Framework\Search\Request\FilterInterface::TYPE_TERM + && in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true) + ) { + $isApplied = $this->termDropdownStrategy->apply($filter, $select); + } elseif ($attribute->getBackendType() === AbstractAttribute::TYPE_STATIC) { + $isApplied = $this->staticAttributeStrategy->apply($filter, $select); + } + } + } + + return $isApplied; + } + + /** + * @param string $field + * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getAttributeByCode($field) + { + return $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field); + } +} diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterStrategyInterface.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterStrategyInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..cf17f7d5132efbf8246af0de674d7c789551aa3e --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterStrategyInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + +/** + * FilterStrategyInterface provides the interface to work with strategies + */ +interface FilterStrategyInterface +{ + /** + * @param \Magento\Framework\Search\Request\FilterInterface $filter + * @param \Magento\Framework\DB\Select $select + * @return bool is filter was applied + */ + public function apply( + \Magento\Framework\Search\Request\FilterInterface $filter, + \Magento\Framework\DB\Select $select + ); +} diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StaticAttributeStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StaticAttributeStrategy.php new file mode 100644 index 0000000000000000000000000000000000000000..eb9d61b5a7f4c352d23096050a5acd292dc80d54 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/StaticAttributeStrategy.php @@ -0,0 +1,74 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\Eav\Model\Config as EavConfig; + +/** + * This strategy handles static attributes + */ +class StaticAttributeStrategy implements FilterStrategyInterface +{ + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resourceConnection; + + /** + * @var AliasResolver + */ + private $aliasResolver; + + /** + * @var EavConfig + */ + private $eavConfig; + + /** + * @param \Magento\Framework\App\ResourceConnection $resourceConnection + * @param EavConfig $eavConfig + * @param AliasResolver $aliasResolver + */ + public function __construct( + \Magento\Framework\App\ResourceConnection $resourceConnection, + EavConfig $eavConfig, + AliasResolver $aliasResolver + ) { + $this->resourceConnection = $resourceConnection; + $this->eavConfig = $eavConfig; + $this->aliasResolver = $aliasResolver; + } + + /** + * {@inheritDoc} + */ + public function apply( + \Magento\Framework\Search\Request\FilterInterface $filter, + \Magento\Framework\DB\Select $select + ) { + $attribute = $this->getAttributeByCode($filter->getField()); + $alias = $this->aliasResolver->getAlias($filter); + $select->joinInner( + [$alias => $attribute->getBackendTable()], + 'search_index.entity_id = ' + . $this->resourceConnection->getConnection()->quoteIdentifier("$alias.entity_id"), + [] + ); + return true; + } + + /** + * @param string $field + * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getAttributeByCode($field) + { + return $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field); + } +} diff --git a/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php new file mode 100644 index 0000000000000000000000000000000000000000..76828fe28f4348c8b90c9f1bf695906242c77222 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php @@ -0,0 +1,127 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Model\Search\FilterMapper; + +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\Eav\Model\Config as EavConfig; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Store\Model\ScopeInterface; +use Magento\Store\Model\StoreManagerInterface; + +/** + * This strategy handles attributes which comply with two criteria: + * - The filter for dropdown or multi-select attribute + * - The filter is Term filter + * + */ +class TermDropdownStrategy implements FilterStrategyInterface +{ + /** + * @var AliasResolver + */ + private $aliasResolver; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var EavConfig + */ + private $eavConfig; + + /** + * @var ResourceConnection + */ + private $resourceConnection; + + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @param StoreManagerInterface $storeManager + * @param ResourceConnection $resourceConnection + * @param EavConfig $eavConfig + * @param ScopeConfigInterface $scopeConfig + * @param AliasResolver $aliasResolver + */ + public function __construct( + StoreManagerInterface $storeManager, + ResourceConnection $resourceConnection, + EavConfig $eavConfig, + ScopeConfigInterface $scopeConfig, + AliasResolver $aliasResolver + ) { + $this->storeManager = $storeManager; + $this->resourceConnection = $resourceConnection; + $this->eavConfig = $eavConfig; + $this->scopeConfig = $scopeConfig; + $this->aliasResolver = $aliasResolver; + } + + /** + * {@inheritDoc} + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function apply( + \Magento\Framework\Search\Request\FilterInterface $filter, + \Magento\Framework\DB\Select $select + ) { + $alias = $this->aliasResolver->getAlias($filter); + $attribute = $this->getAttributeByCode($filter->getField()); + $joinCondition = sprintf( + 'search_index.entity_id = %1$s.entity_id AND %1$s.attribute_id = %2$d AND %1$s.store_id = %3$d', + $alias, + $attribute->getId(), + $this->storeManager->getWebsite()->getId() + ); + $select->joinLeft( + [$alias => $this->resourceConnection->getTableName('catalog_product_index_eav')], + $joinCondition, + [] + ); + if ($this->isAddStockFilter()) { + $stockAlias = $alias . AliasResolver::STOCK_FILTER_SUFFIX; + $select->joinLeft( + [ + $stockAlias => $this->resourceConnection->getTableName('cataloginventory_stock_status'), + ], + sprintf('%2$s.product_id = %1$s.source_id', $alias, $stockAlias), + [] + ); + } + + return true; + } + + /** + * @param string $field + * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute + * @throws \Magento\Framework\Exception\LocalizedException + */ + private function getAttributeByCode($field) + { + return $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field); + } + + /** + * @return bool + */ + private function isAddStockFilter() + { + $isShowOutOfStock = $this->scopeConfig->isSetFlag( + 'cataloginventory/options/show_out_of_stock', + ScopeInterface::SCOPE_STORE + ); + + return false === $isShowOutOfStock; + } +} diff --git a/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php b/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php index 1d30bb4a14d23a94d79a2faa5bc20823df220d8d..0e96e4de700259c9dc48c8f876983e2642316a86 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php +++ b/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php @@ -99,6 +99,7 @@ class IndexBuilder implements IndexBuilderInterface * * @param RequestInterface $request * @return Select + * @throws \LogicException */ public function build(RequestInterface $request) { @@ -132,7 +133,7 @@ class IndexBuilder implements IndexBuilderInterface ), [] ); - $select->where('stock_index.stock_status = ?', Stock::DEFAULT_STOCK_ID); + $select->where('stock_index.stock_status = ?', Stock::STOCK_IN_STOCK); } return $select; diff --git a/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php b/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php index ca7298e1beaac560ee66427f2b9d57db7ce21508..ac726192856a5fa555eae6757cd83b16930565ee 100644 --- a/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php +++ b/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php @@ -6,10 +6,11 @@ namespace Magento\CatalogSearch\Model\Search; -use Magento\Catalog\Model\Product; use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\CatalogSearch\Model\Search\FilterMapper\FilterStrategyInterface; use Magento\Eav\Model\Config as EavConfig; -use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection as AppResource; use Magento\Framework\DB\Select; @@ -21,12 +22,15 @@ use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface; use Magento\Store\Model\StoreManagerInterface; /** + * Responsibility of the TableMapper is to collect all filters from the search query + * and pass them one by one for processing in the FilterContext, + * which will apply them to the Select * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TableMapper { /** - * @var Resource + * @var AppResource */ private $resource; @@ -40,22 +44,59 @@ class TableMapper */ private $eavConfig; + /** + * @var ScopeConfigInterface + */ + private $scopeConfig; + + /** + * @var FilterStrategyInterface + */ + private $filterStrategy; + + /** + * @var AliasResolver + */ + private $aliasResolver; + /** * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @param AppResource $resource * @param StoreManagerInterface $storeManager * @param CollectionFactory $attributeCollectionFactory * @param EavConfig $eavConfig + * @param ScopeConfigInterface $scopeConfig + * @param FilterStrategyInterface $filterStrategy + * @param AliasResolver $aliasResolver */ public function __construct( AppResource $resource, StoreManagerInterface $storeManager, CollectionFactory $attributeCollectionFactory, - EavConfig $eavConfig = null + EavConfig $eavConfig = null, + ScopeConfigInterface $scopeConfig = null, + FilterStrategyInterface $filterStrategy = null, + AliasResolver $aliasResolver = null ) { $this->resource = $resource; $this->storeManager = $storeManager; - $this->eavConfig = $eavConfig !== null ? $eavConfig : ObjectManager::getInstance()->get(EavConfig::class); + + if (null === $eavConfig) { + $eavConfig = ObjectManager::getInstance()->get(EavConfig::class); + } + if (null === $scopeConfig) { + $scopeConfig = ObjectManager::getInstance()->get(ScopeConfigInterface::class); + } + if (null === $filterStrategy) { + $filterStrategy = ObjectManager::getInstance()->get(FilterStrategyInterface::class); + } + if (null === $aliasResolver) { + $aliasResolver = ObjectManager::getInstance()->get(AliasResolver::class); + } + $this->eavConfig = $eavConfig; + $this->scopeConfig = $scopeConfig; + $this->filterStrategy = $filterStrategy; + $this->aliasResolver = $aliasResolver; } /** @@ -66,111 +107,53 @@ class TableMapper */ public function addTables(Select $select, RequestInterface $request) { - $mappedTables = []; - $filters = $this->getFilters($request->getQuery()); + $appliedFilters = []; + $filters = $this->getFiltersFromQuery($request->getQuery()); foreach ($filters as $filter) { - list($alias, $table, $mapOn, $mappedFields, $joinType) = $this->getMappingData($filter); - if (!array_key_exists($alias, $mappedTables)) { - switch ($joinType) { - case \Magento\Framework\DB\Select::INNER_JOIN: - $select->joinInner( - [$alias => $table], - $mapOn, - $mappedFields - ); - break; - case \Magento\Framework\DB\Select::LEFT_JOIN: - $select->joinLeft( - [$alias => $table], - $mapOn, - $mappedFields - ); - break; - default: - throw new \LogicException(__('Unsupported join type: %1', $joinType)); + $alias = $this->aliasResolver->getAlias($filter); + if (!array_key_exists($alias, $appliedFilters)) { + $isApplied = $this->filterStrategy->apply($filter, $select); + if ($isApplied) { + $appliedFilters[$alias] = true; } - $mappedTables[$alias] = $table; } } return $select; } /** + * This method is deprecated. + * Please use \Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver::getAlias() instead. + * + * @deprecated + * @see AliasResolver::getAlias() + * * @param FilterInterface $filter * @return string */ public function getMappingAlias(FilterInterface $filter) { - list($alias) = $this->getMappingData($filter); - return $alias; - } - - /** - * Returns mapping data for field in format: [ - * 'table_alias', - * 'table', - * 'join_condition', - * ['fields'], - * 'joinType' - * ] - * @param FilterInterface $filter - * @return array - */ - private function getMappingData(FilterInterface $filter) - { - $alias = null; - $table = null; - $mapOn = null; - $mappedFields = null; - $field = $filter->getField(); - $joinType = \Magento\Framework\DB\Select::INNER_JOIN; - $fieldToTableMap = $this->getFieldToTableMap($field); - if ($fieldToTableMap) { - list($alias, $table, $mapOn, $mappedFields) = $fieldToTableMap; - $table = $this->resource->getTableName($table); - } elseif ($attribute = $this->getAttributeByCode($field)) { - if ($filter->getType() === FilterInterface::TYPE_TERM - && in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true) - ) { - $joinType = \Magento\Framework\DB\Select::LEFT_JOIN; - $table = $this->resource->getTableName('catalog_product_index_eav'); - $alias = $field . RequestGenerator::FILTER_SUFFIX; - $mapOn = sprintf( - 'search_index.entity_id = %1$s.entity_id AND %1$s.attribute_id = %2$d AND %1$s.store_id = %3$d', - $alias, - $attribute->getId(), - $this->getStoreId() - ); - $mappedFields = []; - } elseif ($attribute->getBackendType() === AbstractAttribute::TYPE_STATIC) { - $table = $attribute->getBackendTable(); - $alias = $field . RequestGenerator::FILTER_SUFFIX; - $mapOn = 'search_index.entity_id = ' . $alias . '.entity_id'; - $mappedFields = null; - } - } - - return [$alias, $table, $mapOn, $mappedFields, $joinType]; + return $this->aliasResolver->getAlias($filter); } /** * @param RequestQueryInterface $query * @return FilterInterface[] */ - private function getFilters($query) + private function getFiltersFromQuery(RequestQueryInterface $query) { $filters = []; switch ($query->getType()) { case RequestQueryInterface::TYPE_BOOL: /** @var \Magento\Framework\Search\Request\Query\BoolExpression $query */ foreach ($query->getMust() as $subQuery) { - $filters = array_merge($filters, $this->getFilters($subQuery)); + $filters = array_merge($filters, $this->getFiltersFromQuery($subQuery)); } foreach ($query->getShould() as $subQuery) { - $filters = array_merge($filters, $this->getFilters($subQuery)); + $filters = array_merge($filters, $this->getFiltersFromQuery($subQuery)); } foreach ($query->getMustNot() as $subQuery) { - $filters = array_merge($filters, $this->getFilters($subQuery)); + $filters = array_merge($filters, $this->getFiltersFromQuery($subQuery)); } break; case RequestQueryInterface::TYPE_FILTER: @@ -219,57 +202,4 @@ class TableMapper } return $filters; } - - /** - * @return int - */ - private function getWebsiteId() - { - return $this->storeManager->getWebsite()->getId(); - } - - /** - * @return int - */ - private function getStoreId() - { - return $this->storeManager->getStore()->getId(); - } - - /** - * @param string $field - * @return array|null - */ - private function getFieldToTableMap($field) - { - $fieldToTableMap = [ - 'price' => [ - 'price_index', - 'catalog_product_index_price', - $this->resource->getConnection()->quoteInto( - 'search_index.entity_id = price_index.entity_id AND price_index.website_id = ?', - $this->getWebsiteId() - ), - [] - ], - 'category_ids' => [ - 'category_ids_index', - 'catalog_category_product_index', - 'search_index.entity_id = category_ids_index.product_id', - [] - ] - ]; - return array_key_exists($field, $fieldToTableMap) ? $fieldToTableMap[$field] : null; - } - - /** - * @param string $field - * @return \Magento\Catalog\Model\ResourceModel\Eav\Attribute - * @throws \Magento\Framework\Exception\LocalizedException - */ - private function getAttributeByCode($field) - { - $attribute = $this->eavConfig->getAttribute(Product::ENTITY, $field); - return $attribute; - } } diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ab01d2553a3edcebde8a564a8ca8b8bb8cf2c7dd --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Test\Unit\Model\Adapter\Mysql\Filter; + +use Magento\CatalogSearch\Model\Search\RequestGenerator; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class AliasResolverTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver + */ + private $aliasResolver; + + /** + * @inheritDoc + */ + protected function setUp() + { + $objectManagerHelper = new ObjectManagerHelper($this); + $this->aliasResolver = $objectManagerHelper->getObject( + \Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver::class, + [] + ); + } + + /** + * @param string $field + * @param string $expectedAlias + * @dataProvider aliasDataProvider + */ + public function testGetFilterAlias($field, $expectedAlias) + { + $filter = $this->getMockBuilder(\Magento\Framework\Search\Request\Filter\Term::class) + ->setMethods(['getField']) + ->disableOriginalConstructor() + ->getMock(); + $filter->expects($this->once()) + ->method('getField') + ->willReturn($field); + $this->assertSame($expectedAlias, $this->aliasResolver->getAlias($filter)); + } + + /** + * @return array + */ + public function aliasDataProvider() + { + return [ + 'general' => [ + 'field' => 'general', + 'alias' => 'general' . RequestGenerator::FILTER_SUFFIX, + ], + 'price' => [ + 'field' => 'price', + 'alias' => 'price_index', + ], + 'category_ids' => [ + 'field' => 'category_ids', + 'alias' => 'category_ids_index', + ], + ]; + } +} diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php index 0949bf6469be92611fbefa4c45962d8255570892..2cd6935586bd816bcc8c1e444b242c3b3f7a1472 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/PreprocessorTest.php @@ -6,6 +6,7 @@ namespace Magento\CatalogSearch\Test\Unit\Model\Adapter\Mysql\Filter; +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; use Magento\Framework\DB\Select; use Magento\Framework\EntityManager\EntityMetadata; use Magento\Framework\Search\Request\FilterInterface; @@ -18,9 +19,9 @@ use PHPUnit_Framework_MockObject_MockObject as MockObject; class PreprocessorTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\CatalogSearch\Model\Search\TableMapper|\PHPUnit_Framework_MockObject_MockObject + * @var AliasResolver|\PHPUnit_Framework_MockObject_MockObject */ - private $tableMapper; + private $aliasResolver; /** * @var \Magento\Framework\DB\Adapter\AdapterInterface|MockObject @@ -141,7 +142,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase ) ); - $this->tableMapper = $this->getMockBuilder(\Magento\CatalogSearch\Model\Search\TableMapper::class) + $this->aliasResolver = $this->getMockBuilder(AliasResolver::class) ->disableOriginalConstructor() ->getMock(); $this->metadataPoolMock = $this->getMockBuilder(\Magento\Framework\EntityManager\MetadataPool::class) @@ -164,7 +165,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase 'resource' => $resource, 'attributePrefix' => 'attr_', 'metadataPool' => $this->metadataPoolMock, - 'tableMapper' => $this->tableMapper, + 'aliasResolver' => $this->aliasResolver, ] ); } @@ -234,7 +235,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase $this->attribute->method('getAttributeCode') ->willReturn('static_attribute'); - $this->tableMapper->expects($this->once())->method('getMappingAlias') + $this->aliasResolver->expects($this->once())->method('getAlias') ->willReturn('attr_table_alias'); $this->filter->expects($this->exactly(3)) ->method('getField') @@ -272,7 +273,7 @@ class PreprocessorTest extends \PHPUnit_Framework_TestCase ->method('getFrontendInput') ->willReturn($frontendInput); - $this->tableMapper->expects($this->once())->method('getMappingAlias') + $this->aliasResolver->expects($this->once())->method('getAlias') ->willReturn('termAttrAlias'); $this->filter->expects($this->exactly(3)) diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/FilterContextTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/FilterContextTest.php new file mode 100644 index 0000000000000000000000000000000000000000..9f133b763889bafb4843f7f9f868a3995a842eed --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/FilterContextTest.php @@ -0,0 +1,236 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\CatalogSearch\Test\Unit\Model\Search\FilterMapper; + +use Magento\Catalog\Model\ResourceModel\Eav\Attribute; +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; +use Magento\CatalogSearch\Model\Search\FilterMapper\ExclusionStrategy; +use Magento\CatalogSearch\Model\Search\FilterMapper\FilterContext; +use Magento\CatalogSearch\Model\Search\FilterMapper\StaticAttributeStrategy; +use Magento\CatalogSearch\Model\Search\FilterMapper\TermDropdownStrategy; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Framework\Search\Request\FilterInterface; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + +class FilterContextTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var FilterContext|\PHPUnit_Framework_MockObject_MockObject + */ + private $filterContext; + + /** + * @var AliasResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $aliasResolver; + + /** + * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject + */ + private $eavConfig; + + /** + * @var ExclusionStrategy|\PHPUnit_Framework_MockObject_MockObject + */ + private $exclusionStrategy; + + /** + * @var TermDropdownStrategy|\PHPUnit_Framework_MockObject_MockObject + */ + private $termDropdownStrategy; + + /** + * @var StaticAttributeStrategy|\PHPUnit_Framework_MockObject_MockObject + */ + private $staticAttributeStrategy; + + /** + * @var \Magento\Framework\DB\Select + */ + private $select; + + /** + * @inheritDoc + */ + protected function setUp() + { + $this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class) + ->disableOriginalConstructor() + ->setMethods(['getAttribute']) + ->getMock(); + $this->aliasResolver = $this->getMockBuilder( + AliasResolver::class + ) + ->disableOriginalConstructor() + ->setMethods(['getAlias']) + ->getMock(); + $this->exclusionStrategy = $this->getMockBuilder(ExclusionStrategy::class) + ->disableOriginalConstructor() + ->setMethods(['apply']) + ->getMock(); + $this->termDropdownStrategy = $this->getMockBuilder(TermDropdownStrategy::class) + ->disableOriginalConstructor() + ->setMethods(['apply']) + ->getMock(); + $this->staticAttributeStrategy = $this->getMockBuilder(StaticAttributeStrategy::class) + ->disableOriginalConstructor() + ->setMethods(['apply']) + ->getMock(); + $this->select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + $objectManager = new ObjectManager($this); + $this->filterContext = $objectManager->getObject( + FilterContext::class, + [ + 'eavConfig' => $this->eavConfig, + 'aliasResolver' => $this->aliasResolver, + 'exclusionStrategy' => $this->exclusionStrategy, + 'termDropdownStrategy' => $this->termDropdownStrategy, + 'staticAttributeStrategy' => $this->staticAttributeStrategy, + ] + ); + } + + public function testApplyOnExclusionFilter() + { + $filter = $this->createFilterMock(); + $this->exclusionStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(true); + $this->eavConfig->expects($this->never())->method('getAttribute'); + $this->assertTrue($this->filterContext->apply($filter, $this->select)); + } + + public function testApplyFilterWithoutAttribute() + { + $filter = $this->createFilterMock('some_field'); + $this->exclusionStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(false); + $this->eavConfig->expects($this->once()) + ->method('getAttribute') + ->with(\Magento\Catalog\Model\Product::ENTITY, 'some_field') + ->willReturn(null); + $this->assertFalse($this->filterContext->apply($filter, $this->select)); + } + + public function testApplyOnTermFilterBySelect() + { + $filter = $this->createFilterMock('select_field', FilterInterface::TYPE_TERM); + $attribute = $this->createAttributeMock('select'); + $this->eavConfig->expects($this->once()) + ->method('getAttribute') + ->with(\Magento\Catalog\Model\Product::ENTITY, 'select_field') + ->willReturn($attribute); + $this->exclusionStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(false); + $this->termDropdownStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(true); + $this->assertTrue($this->filterContext->apply($filter, $this->select)); + } + + public function testApplyOnTermFilterByMultiSelect() + { + $filter = $this->createFilterMock('multiselect_field', FilterInterface::TYPE_TERM); + $attribute = $this->createAttributeMock('multiselect'); + $this->eavConfig->expects($this->once()) + ->method('getAttribute') + ->with(\Magento\Catalog\Model\Product::ENTITY, 'multiselect_field') + ->willReturn($attribute); + $this->exclusionStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(false); + $this->termDropdownStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(true); + $this->assertTrue($this->filterContext->apply($filter, $this->select)); + } + + public function testApplyOnTermFilterByStaticAttribute() + { + $filter = $this->createFilterMock('multiselect_field', FilterInterface::TYPE_TERM); + $attribute = $this->createAttributeMock('text', AbstractAttribute::TYPE_STATIC); + $this->eavConfig->expects($this->once()) + ->method('getAttribute') + ->with(\Magento\Catalog\Model\Product::ENTITY, 'multiselect_field') + ->willReturn($attribute); + $this->exclusionStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(false); + $this->staticAttributeStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(true); + $this->assertTrue($this->filterContext->apply($filter, $this->select)); + } + + public function testApplyOnTermFilterByUnknownAttributeType() + { + $filter = $this->createFilterMock('multiselect_field', FilterInterface::TYPE_TERM); + $attribute = $this->createAttributeMock('text', 'text'); + $this->eavConfig->expects($this->once()) + ->method('getAttribute') + ->with(\Magento\Catalog\Model\Product::ENTITY, 'multiselect_field') + ->willReturn($attribute); + $this->exclusionStrategy->expects($this->once()) + ->method('apply') + ->with($filter, $this->select) + ->willReturn(false); + $this->assertFalse($this->filterContext->apply($filter, $this->select)); + } + + /** + * @param string $field + * @param string $type + * @return FilterInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private function createFilterMock($field = null, $type = null) + { + $filter = $this->getMockBuilder(FilterInterface::class) + ->setMethods(['getField', 'getType']) + ->getMockForAbstractClass(); + $filter->expects($this->any()) + ->method('getField') + ->willReturn($field); + $filter->expects($this->any()) + ->method('getType') + ->willReturn($type); + + return $filter; + } + + /** + * @param string|null $frontendInput + * @param string|null $backendType + * @return Attribute|\PHPUnit_Framework_MockObject_MockObject + */ + private function createAttributeMock($frontendInput = null, $backendType = null) + { + $attribute = $this->getMockBuilder(Attribute::class) + ->disableOriginalConstructor() + ->setMethods(['getFrontendInput', 'getBackendType']) + ->getMock(); + $attribute->expects($this->any()) + ->method('getFrontendInput') + ->willReturn($frontendInput); + $attribute->expects($this->any()) + ->method('getBackendType') + ->willReturn($backendType); + return $attribute; + } +} diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php index dd9a556146ab3e40e4e1182f44c959c8260d9c8d..dad4ad8095f5af27fa3ce7cd48b1803c706d80a6 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/TableMapperTest.php @@ -6,6 +6,9 @@ namespace Magento\CatalogSearch\Test\Unit\Model\Search; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection; +use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory; +use Magento\CatalogSearch\Model\Adapter\Mysql\Filter\AliasResolver; use Magento\Framework\Search\Request\FilterInterface; use Magento\Framework\Search\Request\QueryInterface; use \Magento\Framework\TestFramework\Unit\Helper\ObjectManager; @@ -16,8 +19,10 @@ use \Magento\Framework\TestFramework\Unit\Helper\ObjectManager; */ class TableMapperTest extends \PHPUnit_Framework_TestCase { - const WEBSITE_ID = 4512; - const STORE_ID = 2514; + /** + * @var AliasResolver|\PHPUnit_Framework_MockObject_MockObject + */ + private $aliasResolver; /** * @var \Magento\Eav\Model\Config|\PHPUnit_Framework_MockObject_MockObject @@ -25,7 +30,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase private $eavConfig; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection|\PHPUnit_Framework_MockObject_MockObject + * @var Collection|\PHPUnit_Framework_MockObject_MockObject */ private $attributeCollection; @@ -59,11 +64,6 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase */ private $resource; - /** - * @var \Magento\Store\Api\Data\StoreInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $store; - /** * @var \Magento\CatalogSearch\Model\Search\TableMapper */ @@ -76,65 +76,49 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase $this->connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->connection->expects($this->any()) - ->method('quoteInto') - ->willReturnCallback( - function ($query, $expression) { - return str_replace('?', $expression, $query); - } - ); + $this->connection->expects($this->never())->method('quoteInto'); $this->resource = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) ->disableOriginalConstructor() ->getMock(); - $this->resource->method('getTableName') - ->willReturnCallback( - function ($table) { - return 'prefix_' . $table; - } - ); - $this->resource->expects($this->any()) - ->method('getConnection') - ->willReturn($this->connection); + $this->resource->expects($this->never())->method('getTableName'); + $this->resource->expects($this->never())->method('getConnection'); $this->website = $this->getMockBuilder(\Magento\Store\Api\Data\WebsiteInterface::class) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $this->website->expects($this->any()) - ->method('getId') - ->willReturn(self::WEBSITE_ID); - $this->store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $this->store->expects($this->any()) - ->method('getId') - ->willReturn(self::STORE_ID); + $this->website->expects($this->never())->method('getId'); + $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) ->disableOriginalConstructor() ->getMock(); - $this->storeManager->expects($this->any()) - ->method('getWebsite') - ->willReturn($this->website); - $this->storeManager->expects($this->any()) - ->method('getStore') - ->willReturn($this->store); - $this->attributeCollection = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection::class - ) + $this->storeManager->expects($this->never())->method('getWebsite'); + $this->storeManager->expects($this->never())->method('getStore'); + + $this->attributeCollection = $this->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); - $attributeCollectionFactory = $this->getMockBuilder( - \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory::class - ) + $attributeCollectionFactory = $this->getMockBuilder(CollectionFactory::class) ->setMethods(['create']) ->disableOriginalConstructor() ->getMock(); $attributeCollectionFactory->expects($this->never()) ->method('create'); + $this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class) ->setMethods(['getAttribute']) ->disableOriginalConstructor() ->getMock(); + + $this->aliasResolver = $this->getMockBuilder(AliasResolver::class) + ->disableOriginalConstructor() + ->getMock(); + $this->aliasResolver->expects($this->any()) + ->method('getAlias') + ->willReturnCallback(function (FilterInterface $filter) { + return $filter->getField() . '_alias'; + }); + $this->target = $objectManager->getObject( \Magento\CatalogSearch\Model\Search\TableMapper::class, [ @@ -142,6 +126,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase 'storeManager' => $this->storeManager, 'attributeCollectionFactory' => $attributeCollectionFactory, 'eavConfig' => $this->eavConfig, + 'aliasResolver' => $this->aliasResolver, ] ); @@ -160,14 +145,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); - $this->select->expects($this->once()) - ->method('joinInner') - ->with( - ['price_index' => 'prefix_catalog_product_index_price'], - 'search_index.entity_id = price_index.entity_id AND price_index.website_id = ' . self::WEBSITE_ID, - [] - ) - ->willReturnSelf(); + $select = $this->target->addTables($this->select, $this->request); $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select'); } @@ -176,18 +154,10 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase { $priceFilter = $this->createRangeFilter('static'); $query = $this->createFilterQuery($priceFilter); - $this->createAttributeMock('static', 'static', 'backend_table', 0, 'select'); $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); - $this->select->expects($this->once()) - ->method('joinInner') - ->with( - ['static_filter' => 'backend_table'], - 'search_index.entity_id = static_filter.entity_id', - null - ) - ->willReturnSelf(); + $select = $this->target->addTables($this->select, $this->request); $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select'); } @@ -199,46 +169,25 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); - $this->select->expects($this->once()) - ->method('joinInner') - ->with( - ['category_ids_index' => 'prefix_catalog_category_product_index'], - 'search_index.entity_id = category_ids_index.product_id', - [] - ) - ->willReturnSelf(); + $select = $this->target->addTables($this->select, $this->request); $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select'); } public function testAddTermFilter() { - $this->createAttributeMock('color', null, null, 132, 'select', 0); $categoryIdsFilter = $this->createTermFilter('color'); $query = $this->createFilterQuery($categoryIdsFilter); $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); - $this->select->expects($this->once()) - ->method('joinLeft') - ->with( - ['color_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = color_filter.entity_id' - . ' AND color_filter.attribute_id = 132' - . ' AND color_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); + $select = $this->target->addTables($this->select, $this->request); $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select'); } public function testAddBoolQueryWithTermFiltersInside() { - $this->createAttributeMock('must1', null, null, 101, 'select', 0); - $this->createAttributeMock('should1', null, null, 102, 'select', 1); - $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2); - $query = $this->createBoolQuery( [ $this->createFilterQuery($this->createTermFilter('must1')), @@ -253,45 +202,13 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); - $this->select->expects($this->at(0)) - ->method('joinLeft') - ->with( - ['must1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = must1_filter.entity_id' - . ' AND must1_filter.attribute_id = 101' - . ' AND must1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(1)) - ->method('joinLeft') - ->with( - ['should1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = should1_filter.entity_id' - . ' AND should1_filter.attribute_id = 102' - . ' AND should1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(2)) - ->method('joinLeft') - ->with( - ['mustNot1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = mustNot1_filter.entity_id' - . ' AND mustNot1_filter.attribute_id = 103' - . ' AND mustNot1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); + $select = $this->target->addTables($this->select, $this->request); $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select'); } public function testAddBoolQueryWithTermAndPriceFiltersInside() { - $this->createAttributeMock('must1', null, null, 101, 'select', 0); - $this->createAttributeMock('should1', null, null, 102, 'select', 1); - $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2); $query = $this->createBoolQuery( [ $this->createFilterQuery($this->createTermFilter('must1')), @@ -307,53 +224,13 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); - $this->select->expects($this->at(0)) - ->method('joinLeft') - ->with( - ['must1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = must1_filter.entity_id' - . ' AND must1_filter.attribute_id = 101' - . ' AND must1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(1)) - ->method('joinInner') - ->with( - ['price_index' => 'prefix_catalog_product_index_price'], - 'search_index.entity_id = price_index.entity_id AND price_index.website_id = ' . self::WEBSITE_ID, - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(2)) - ->method('joinLeft') - ->with( - ['should1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = should1_filter.entity_id' - . ' AND should1_filter.attribute_id = 102' - . ' AND should1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(3)) - ->method('joinLeft') - ->with( - ['mustNot1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = mustNot1_filter.entity_id' - . ' AND mustNot1_filter.attribute_id = 103' - . ' AND mustNot1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); + $select = $this->target->addTables($this->select, $this->request); $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select'); } public function testAddBoolFilterWithTermFiltersInside() { - $this->createAttributeMock('must1', null, null, 101, 'select', 0); - $this->createAttributeMock('should1', null, null, 102, 'select', 1); - $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2); $query = $this->createFilterQuery( $this->createBoolFilter( [ @@ -370,45 +247,13 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); - $this->select->expects($this->at(0)) - ->method('joinLeft') - ->with( - ['must1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = must1_filter.entity_id' - . ' AND must1_filter.attribute_id = 101' - . ' AND must1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(1)) - ->method('joinLeft') - ->with( - ['should1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = should1_filter.entity_id' - . ' AND should1_filter.attribute_id = 102' - . ' AND should1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(2)) - ->method('joinLeft') - ->with( - ['mustNot1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = mustNot1_filter.entity_id' - . ' AND mustNot1_filter.attribute_id = 103' - . ' AND mustNot1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); + $select = $this->target->addTables($this->select, $this->request); $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select'); } public function testAddBoolFilterWithBoolFiltersInside() { - $this->createAttributeMock('must1', null, null, 101, 'select', 0); - $this->createAttributeMock('should1', null, null, 102, 'select', 1); - $this->createAttributeMock('mustNot1', null, null, 103, 'select', 2); $query = $this->createFilterQuery( $this->createBoolFilter( [ @@ -425,36 +270,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase $this->request->expects($this->once()) ->method('getQuery') ->willReturn($query); - $this->select->expects($this->at(0)) - ->method('joinLeft') - ->with( - ['must1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = must1_filter.entity_id' - . ' AND must1_filter.attribute_id = 101' - . ' AND must1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(1)) - ->method('joinLeft') - ->with( - ['should1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = should1_filter.entity_id' - . ' AND should1_filter.attribute_id = 102' - . ' AND should1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); - $this->select->expects($this->at(2)) - ->method('joinLeft') - ->with( - ['mustNot1_filter' => 'prefix_catalog_product_index_eav'], - 'search_index.entity_id = mustNot1_filter.entity_id' - . ' AND mustNot1_filter.attribute_id = 103' - . ' AND mustNot1_filter.store_id = 2514', - [] - ) - ->willReturnSelf(); + $select = $this->target->addTables($this->select, $this->request); $this->assertEquals($this->select, $select, 'Returned results isn\'t equal to passed select'); } @@ -472,6 +288,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase ->willReturn(QueryInterface::TYPE_FILTER); $query->method('getReference') ->willReturn($filter); + return $query; } @@ -495,6 +312,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase ->willReturn($should); $query->method('getMustNot') ->willReturn($mustNot); + return $query; } @@ -518,6 +336,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase ->willReturn($should); $query->method('getMustNot') ->willReturn($mustNot); + return $query; } @@ -532,6 +351,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase FilterInterface::TYPE_RANGE, $field ); + return $filter; } @@ -546,6 +366,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase FilterInterface::TYPE_TERM, $field ); + return $filter; } @@ -564,40 +385,7 @@ class TableMapperTest extends \PHPUnit_Framework_TestCase ->willReturn($type); $filter->method('getField') ->willReturn($field); - return $filter; - } - /** - * @param string $code - * @param string $backendType - * @param string $backendTable - * @param int $attributeId - * @param string $frontendInput - * @param int $positionInCollection - */ - private function createAttributeMock( - $code, - $backendType = null, - $backendTable = null, - $attributeId = 120, - $frontendInput = 'select', - $positionInCollection = 0 - ) { - $attribute = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) - ->setMethods(['getBackendType', 'getBackendTable', 'getId', 'getFrontendInput']) - ->disableOriginalConstructor() - ->getMock(); - $attribute->method('getId') - ->willReturn($attributeId); - $attribute->method('getBackendType') - ->willReturn($backendType); - $attribute->method('getBackendTable') - ->willReturn($backendTable); - $attribute->method('getFrontendInput') - ->willReturn($frontendInput); - $this->eavConfig->expects($this->at($positionInCollection)) - ->method('getAttribute') - ->with(\Magento\Catalog\Model\Product::ENTITY, $code) - ->willReturn($attribute); + return $filter; } } diff --git a/app/code/Magento/CatalogSearch/composer.json b/app/code/Magento/CatalogSearch/composer.json index 49756420bd2308cd1f8326f474395b6209c7450c..313cc99881a318799c15488fa742151e26db18ec 100644 --- a/app/code/Magento/CatalogSearch/composer.json +++ b/app/code/Magento/CatalogSearch/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-search", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-search": "100.2.*", diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml index f62b4e47767a25230c5c2ad8372f99acc6bda2eb..7e9451f9b83e7ada30455509b8fd686a0c251705 100644 --- a/app/code/Magento/CatalogSearch/etc/di.xml +++ b/app/code/Magento/CatalogSearch/etc/di.xml @@ -11,6 +11,7 @@ <preference for="Magento\Framework\Search\Adapter\Mysql\Filter\PreprocessorInterface" type="Magento\CatalogSearch\Model\Adapter\Mysql\Filter\Preprocessor" /> <preference for="Magento\Framework\Search\Dynamic\DataProviderInterface" type="Magento\CatalogSearch\Model\Adapter\Mysql\Dynamic\DataProvider" /> <preference for="Magento\Framework\Search\Adapter\OptionsInterface" type="Magento\CatalogSearch\Model\Adapter\Options" /> + <preference for="Magento\CatalogSearch\Model\Search\FilterMapper\FilterStrategyInterface" type="Magento\CatalogSearch\Model\Search\FilterMapper\FilterContext"/> <type name="Magento\CatalogSearch\Model\Indexer\IndexerHandlerFactory"> <arguments> <argument name="configPath" xsi:type="const">Magento\CatalogSearch\Model\ResourceModel\EngineInterface::CONFIG_ENGINE_PATH</argument> diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json index 8fd54cbc8ee8319f625cd6320954a0c821bf9a52..a5f66cd09dda49086434a4dc73b073fe7f0ee9c3 100644 --- a/app/code/Magento/CatalogUrlRewrite/composer.json +++ b/app/code/Magento/CatalogUrlRewrite/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-url-rewrite", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-catalog-import-export": "100.2.*", diff --git a/app/code/Magento/CatalogWidget/composer.json b/app/code/Magento/CatalogWidget/composer.json index 330f46176286c70822a32762dccfed157a2ce358..198e54db32d888dfc32eafd33f5c6b3ca45da870 100644 --- a/app/code/Magento/CatalogWidget/composer.json +++ b/app/code/Magento/CatalogWidget/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-catalog-widget", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-widget": "100.2.*", "magento/module-backend": "100.2.*", diff --git a/app/code/Magento/Checkout/Controller/Cart/Add.php b/app/code/Magento/Checkout/Controller/Cart/Add.php index fddfa63b5983e5f508f5211c19f8980d56aafc34..bfbf0008bf5892067aaa7b0ba53ca1bbb7632339 100644 --- a/app/code/Magento/Checkout/Controller/Cart/Add.php +++ b/app/code/Magento/Checkout/Controller/Cart/Add.php @@ -84,6 +84,7 @@ class Add extends \Magento\Checkout\Controller\Cart } $params = $this->getRequest()->getParams(); + try { if (isset($params['qty'])) { $filter = new \Zend_Filter_LocalizedToNormalized( diff --git a/app/code/Magento/Checkout/Model/Cart.php b/app/code/Magento/Checkout/Model/Cart.php index 4ce23c9c7f7090980d48f4c3bb231d4214a7e893..236c716f572d91e4bd4b88366414480567ccabc0 100644 --- a/app/code/Magento/Checkout/Model/Cart.php +++ b/app/code/Magento/Checkout/Model/Cart.php @@ -91,6 +91,11 @@ class Cart extends DataObject implements CartInterface */ protected $productRepository; + /** + * @var \Magento\Checkout\Model\Cart\RequestInfoFilterInterface + */ + private $requestInfoFilter; + /** * @param \Magento\Framework\Event\ManagerInterface $eventManager * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig @@ -315,6 +320,7 @@ class Cart extends DataObject implements CartInterface * * @param \Magento\Framework\DataObject|int|array $requestInfo * @return \Magento\Framework\DataObject + * @throws \Magento\Framework\Exception\LocalizedException */ protected function _getProductRequest($requestInfo) { @@ -322,11 +328,14 @@ class Cart extends DataObject implements CartInterface $request = $requestInfo; } elseif (is_numeric($requestInfo)) { $request = new \Magento\Framework\DataObject(['qty' => $requestInfo]); - } else { + } elseif (is_array($requestInfo)) { $request = new \Magento\Framework\DataObject($requestInfo); + } else { + throw new \Magento\Framework\Exception\LocalizedException( + __('We found an invalid request for adding product to quote.') + ); } - - !$request->hasFormKey() ?: $request->unsFormKey(); + $this->getRequestInfoFilter()->filter($request); return $request; } @@ -722,4 +731,19 @@ class Cart extends DataObject implements CartInterface $this->_checkoutSession->setLastAddedProductId($productId); return $result; } + + /** + * Getter for RequestInfoFilter + * + * @deprecated + * @return \Magento\Checkout\Model\Cart\RequestInfoFilterInterface + */ + private function getRequestInfoFilter() + { + if ($this->requestInfoFilter === null) { + $this->requestInfoFilter = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Checkout\Model\Cart\RequestInfoFilterInterface::class); + } + return $this->requestInfoFilter; + } } diff --git a/app/code/Magento/Checkout/Model/Cart/RequestInfoFilter.php b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilter.php new file mode 100644 index 0000000000000000000000000000000000000000..10f3b81386b8febcee8f2b49ee4acd6242d1e5e8 --- /dev/null +++ b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilter.php @@ -0,0 +1,44 @@ +<?php +/** + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Checkout\Model\Cart; + +/** + * Class RequestInfoFilter used for filtering data from a request + */ +class RequestInfoFilter implements RequestInfoFilterInterface +{ + /** + * @var array $params + */ + private $filterList; + + /** + * @param array $filterList + */ + public function __construct( + array $filterList = [] + ) { + $this->filterList = $filterList; + } + + /** + * Filters the data with values from filterList + * + * @param \Magento\Framework\DataObject $params + * @return $this + */ + public function filter(\Magento\Framework\DataObject $params) + { + foreach ($this->filterList as $filterKey) { + /** @var string $filterKey */ + if ($params->hasData($filterKey)) { + $params->unsetData($filterKey); + } + } + return $this; + } +} diff --git a/app/code/Magento/Checkout/Model/Cart/RequestInfoFilterComposite.php b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilterComposite.php new file mode 100644 index 0000000000000000000000000000000000000000..2ef24c0a5f28a198be28cbb4fc0ba212ebbd9097 --- /dev/null +++ b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilterComposite.php @@ -0,0 +1,41 @@ +<?php +/** + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Checkout\Model\Cart; + +/** + * Class RequestInfoFilterComposite + */ +class RequestInfoFilterComposite implements RequestInfoFilterInterface +{ + /** + * @var RequestInfoFilter[] $params + */ + private $filters = []; + + /** + * @param RequestInfoFilter[] $filters + */ + public function __construct( + $filters = [] + ) { + $this->filters = $filters; + } + + /** + * Loops through all leafs of the composite and calls filter method + * + * @param \Magento\Framework\DataObject $params + * @return $this + */ + public function filter(\Magento\Framework\DataObject $params) + { + foreach ($this->filters as $filter) { + $filter->filter($params); + } + return $this; + } +} diff --git a/app/code/Magento/Checkout/Model/Cart/RequestInfoFilterInterface.php b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilterInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..4bd268f6c896e149bf042ccdbaaa85e411ec6153 --- /dev/null +++ b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilterInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Checkout\Model\Cart; + +/** + * Interface RequestInfoFilterInterface used by composite and leafs to implement filtering + */ +interface RequestInfoFilterInterface +{ + /** + * Filters the data object by an array of parameters + * + * @param \Magento\Framework\DataObject $params + * @return RequestInfoFilterInterface + */ + public function filter(\Magento\Framework\DataObject $params); +} diff --git a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php index b07e90384c1647b51d59405022bb63a0b8e108d3..f2ad294d06cbe45d0b5d77ebe8658d546d27efd3 100644 --- a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php +++ b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php @@ -9,6 +9,9 @@ namespace Magento\Checkout\Model; use Magento\Quote\Api\CartRepositoryInterface; use Magento\Framework\Exception\CouldNotSaveException; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPaymentInformationManagementInterface { @@ -42,6 +45,11 @@ class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPa */ protected $cartRepository; + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + /** * @param \Magento\Quote\Api\GuestBillingAddressManagementInterface $billingAddressManagement * @param \Magento\Quote\Api\GuestPaymentMethodManagementInterface $paymentMethodManagement @@ -79,7 +87,13 @@ class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPa $this->savePaymentInformation($cartId, $email, $paymentMethod, $billingAddress); try { $orderId = $this->cartManagement->placeOrder($cartId); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + throw new CouldNotSaveException( + __($e->getMessage()), + $e + ); } catch (\Exception $e) { + $this->getLogger()->critical($e); throw new CouldNotSaveException( __('An error occurred on the server. Please try to place the order again.'), $e @@ -117,4 +131,18 @@ class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPa $quoteIdMask = $this->quoteIdMaskFactory->create()->load($cartId, 'masked_id'); return $this->paymentInformationManagement->getPaymentInformation($quoteIdMask->getQuoteId()); } + + /** + * Get logger instance + * + * @return \Psr\Log\LoggerInterface + * @deprecated + */ + private function getLogger() + { + if (!$this->logger) { + $this->logger = \Magento\Framework\App\ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class); + } + return $this->logger; + } } diff --git a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php index 140917dcdfeff22eaa73b9a1dc928d5d83f6ccd5..79e76feb4366190cb1d81389e7c54ac4f97cca9e 100644 --- a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php +++ b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php @@ -7,6 +7,9 @@ namespace Magento\Checkout\Model; use Magento\Framework\Exception\CouldNotSaveException; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInformationManagementInterface { /** @@ -34,6 +37,11 @@ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInfor */ protected $cartTotalsRepository; + /** + * @var \Psr\Log\LoggerInterface + */ + private $logger; + /** * @param \Magento\Quote\Api\BillingAddressManagementInterface $billingAddressManagement * @param \Magento\Quote\Api\PaymentMethodManagementInterface $paymentMethodManagement @@ -67,7 +75,13 @@ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInfor $this->savePaymentInformation($cartId, $paymentMethod, $billingAddress); try { $orderId = $this->cartManagement->placeOrder($cartId); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + throw new CouldNotSaveException( + __($e->getMessage()), + $e + ); } catch (\Exception $e) { + $this->getLogger()->critical($e); throw new CouldNotSaveException( __('An error occurred on the server. Please try to place the order again.'), $e @@ -102,4 +116,18 @@ class PaymentInformationManagement implements \Magento\Checkout\Api\PaymentInfor $paymentDetails->setTotals($this->cartTotalsRepository->get($cartId)); return $paymentDetails; } + + /** + * Get logger instance + * + * @return \Psr\Log\LoggerInterface + * @deprecated + */ + private function getLogger() + { + if (!$this->logger) { + $this->logger = \Magento\Framework\App\ObjectManager::getInstance()->get(\Psr\Log\LoggerInterface::class); + } + return $this->logger; + } } diff --git a/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestInfoFilterCompositeTest.php b/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestInfoFilterCompositeTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6c758cf4661fdab253a58b04f70969dc845e89d1 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestInfoFilterCompositeTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Checkout\Test\Unit\Model\Cart; + +/** + * Class RequestInfoFilterTest + */ +class RequestInfoFilterCompositeTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Checkout\Model\Cart\RequestInfoFilterComposite + */ + protected $model; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $objectManager; + + /** + * Setup the test + */ + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $requestInfoFilterMock1 = $this->getMock( + \Magento\Checkout\Model\Cart\RequestInfoFilter::class, + ['filter'], + [], + '', + false + ); + $requestInfoFilterMock2 = $this->getMock( + \Magento\Checkout\Model\Cart\RequestInfoFilter::class, + ['filter'], + [], + '', + false + ); + + $requestInfoFilterMock1->expects($this->atLeastOnce()) + ->method('filter'); + $requestInfoFilterMock2->expects($this->atLeastOnce()) + ->method('filter'); + + $filterList = [ $requestInfoFilterMock1, $requestInfoFilterMock2]; + + $this->model = $this->objectManager->getObject( + \Magento\Checkout\Model\Cart\RequestInfoFilterComposite::class, + [ + 'filters' => $filterList, + ] + ); + } + + /** + * Test Filter method + */ + public function testFilter() + { + /** @var \Magento\Framework\DataObject $params */ + $params = $this->objectManager->getObject( + \Magento\Framework\DataObject::class, + ['data' => ['abc' => 1, 'efg' => 1, 'xyz' => 1]] + ); + $result = $this->model->filter($params); + $this->assertEquals($this->model, $result); + } +} diff --git a/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestInfoFilterTest.php b/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestInfoFilterTest.php new file mode 100644 index 0000000000000000000000000000000000000000..cd6ca330da1b2d7252b8911f1820d7bc217d3561 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestInfoFilterTest.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Checkout\Test\Unit\Model\Cart; + +/** + * Class RequestInfoFilterTest + */ +class RequestInfoFilterTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Checkout\Model\Cart\RequestInfoFilter + */ + protected $model; + + /** + * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager + */ + protected $objectManager; + + /** + * Setup the test + */ + protected function setUp() + { + $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->model = $this->objectManager->getObject( + \Magento\Checkout\Model\Cart\RequestInfoFilter::class, + [ + 'filterList' => ['efg', 'xyz'], + ] + ); + } + + /** + * Test Filter method + */ + public function testFilter() + { + /** @var \Magento\Framework\DataObject $params */ + $params = $this->objectManager->getObject( + \Magento\Framework\DataObject::class, + ['data' => ['abc' => 1, 'efg' => 1, 'xyz' => 1]] + ); + $result = $this->model->filter($params); + $this->assertEquals($this->model, $result); + $this->assertEquals(['abc' => 1], $params->convertToArray()); + } +} diff --git a/app/code/Magento/Checkout/Test/Unit/Model/CartTest.php b/app/code/Magento/Checkout/Test/Unit/Model/CartTest.php index 199e6692e68d1777c382c8125391cac88326a549..9984fe12d3792e95f0b29d0d2c71ff36b37a5897 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/CartTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/CartTest.php @@ -29,7 +29,7 @@ class CartTest extends \PHPUnit_Framework_TestCase */ protected $customerSessionMock; - /** @var \Magento\CatalogInventory\Api\StockItem|\PHPUnit_Framework_MockObject_MockObject */ + /** @var \Magento\CatalogInventory\Api\Data\StockItemInterface|\PHPUnit_Framework_MockObject_MockObject */ protected $stockItemMock; /** @@ -57,16 +57,39 @@ class CartTest extends \PHPUnit_Framework_TestCase */ protected $stockState; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $storeMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $productRepository; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $requestInfoFilterMock; + protected function setUp() { $this->checkoutSessionMock = $this->getMock(\Magento\Checkout\Model\Session::class, [], [], '', false); $this->customerSessionMock = $this->getMock(\Magento\Customer\Model\Session::class, [], [], '', false); $this->scopeConfigMock = $this->getMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); + $this->quoteMock = $this->getMock(\Magento\Quote\Model\Quote::class, [], [], '', false); + $this->eventManagerMock = $this->getMock(\Magento\Framework\Event\ManagerInterface::class); + $this->storeManagerMock = $this->getMock(\Magento\Store\Model\StoreManagerInterface::class); + $this->productRepository = $this->getMock(\Magento\Catalog\Api\ProductRepositoryInterface::class); $this->stockRegistry = $this->getMockBuilder(\Magento\CatalogInventory\Model\StockRegistry::class) ->disableOriginalConstructor() ->setMethods(['getStockItem', '__wakeup']) ->getMock(); - $this->stockItemMock = $this->getMock( \Magento\CatalogInventory\Model\Stock\Item::class, ['getMinSaleQty', '__wakeup'], @@ -74,7 +97,6 @@ class CartTest extends \PHPUnit_Framework_TestCase '', false ); - $this->stockState = $this->getMock( \Magento\CatalogInventory\Model\StockState::class, ['suggestQty', '__wakeup'], @@ -82,12 +104,22 @@ class CartTest extends \PHPUnit_Framework_TestCase '', false ); + $this->storeMock = + $this->getMock(\Magento\Store\Model\Store::class, ['getWebsiteId', 'getId', '__wakeup'], [], '', false); + $this->requestInfoFilterMock = $this->getMock(\Magento\Checkout\Model\Cart\RequestInfoFilterInterface::class); $this->stockRegistry->expects($this->any()) ->method('getStockItem') ->will($this->returnValue($this->stockItemMock)); - $this->quoteMock = $this->getMock(\Magento\Quote\Model\Quote::class, [], [], '', false); - $this->eventManagerMock = $this->getMock(\Magento\Framework\Event\ManagerInterface::class); + $this->storeMock->expects($this->any()) + ->method('getWebsiteId') + ->will($this->returnValue(10)); + $this->storeMock->expects($this->any()) + ->method('getId') + ->will($this->returnValue(10)); + $this->storeManagerMock->expects($this->any()) + ->method('getStore') + ->will($this->returnValue($this->storeMock)); $this->objectManagerHelper = new ObjectManagerHelper($this); $this->cart = $this->objectManagerHelper->getObject( @@ -98,9 +130,14 @@ class CartTest extends \PHPUnit_Framework_TestCase 'stockRegistry' => $this->stockRegistry, 'stockState' => $this->stockState, 'customerSession' => $this->customerSessionMock, - 'eventManager' => $this->eventManagerMock + 'eventManager' => $this->eventManagerMock, + 'storeManager' => $this->storeManagerMock, + 'productRepository' => $this->productRepository ] ); + + $this->objectManagerHelper + ->setBackwardCompatibleProperty($this->cart, 'requestInfoFilter', $this->requestInfoFilterMock); } public function testSuggestItemsQty() @@ -169,10 +206,17 @@ class CartTest extends \PHPUnit_Framework_TestCase */ public function prepareQuoteItemMock($itemId) { - $store = $this->getMock(\Magento\Store\Model\Store::class, ['getWebsiteId', '__wakeup'], [], '', false); + $store = $this->getMock(\Magento\Store\Model\Store::class, ['getId', '__wakeup'], [], '', false); $store->expects($this->any()) ->method('getWebsiteId') ->will($this->returnValue(10)); + $store->expects($this->any()) + ->method('getId') + ->will($this->returnValue(10)); + $this->storeManagerMock->expects($this->any()) + ->method('getStore') + ->will($this->returnValue($store)); + switch ($itemId) { case 2: $product = $this->getMock( @@ -255,4 +299,162 @@ class CartTest extends \PHPUnit_Framework_TestCase ['useQty' => false] ]; } + + /** + * Test successful scenarios for AddProduct + * + * @param int|\Magento\Catalog\Model\Product $productInfo + * @param \Magento\Framework\DataObject|int|array $requestInfo + * @dataProvider addProductDataProvider + */ + public function testAddProduct($productInfo, $requestInfo) + { + $product = $this->getMock( + \Magento\Catalog\Model\Product::class, + ['getStore', 'getWebsiteIds', 'getProductUrl', 'getId', '__wakeup'], + [], + '', + false + ); + $product->expects($this->any()) + ->method('getId') + ->will($this->returnValue(4)); + $product->expects($this->once()) + ->method('getStore') + ->will($this->returnValue($this->storeMock)); + $product->expects($this->any()) + ->method('getWebsiteIds') + ->will($this->returnValue([10])); + $product->expects($this->any()) + ->method('getProductUrl') + ->will($this->returnValue('url')); + $this->productRepository->expects($this->any()) + ->method('getById') + ->will($this->returnValue($product)); + $this->quoteMock->expects($this->once()) + ->method('addProduct') + ->will($this->returnValue(1)); + $this->checkoutSessionMock->expects($this->once()) + ->method('getQuote') + ->will($this->returnValue($this->quoteMock)); + + $this->eventManagerMock->expects($this->at(0))->method('dispatch')->with( + 'checkout_cart_product_add_after', + ['quote_item' => 1, 'product' => $product] + ); + + if (!$productInfo) { + $productInfo = $product; + } + $result = $this->cart->addProduct($productInfo, $requestInfo); + $this->assertSame($this->cart, $result); + } + + /** + * Test exception on adding product for AddProduct + * + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testAddProductException() + { + $product = $this->getMock( + \Magento\Catalog\Model\Product::class, + ['getStore', 'getWebsiteIds', 'getProductUrl', 'getId', '__wakeup'], + [], + '', + false + ); + $product->expects($this->any()) + ->method('getId') + ->will($this->returnValue(4)); + $product->expects($this->once()) + ->method('getStore') + ->will($this->returnValue($this->storeMock)); + $product->expects($this->any()) + ->method('getWebsiteIds') + ->will($this->returnValue([10])); + $product->expects($this->any()) + ->method('getProductUrl') + ->will($this->returnValue('url')); + $this->productRepository->expects($this->any()) + ->method('getById') + ->will($this->returnValue($product)); + $this->quoteMock->expects($this->once()) + ->method('addProduct') + ->will($this->returnValue('error')); + $this->checkoutSessionMock->expects($this->once()) + ->method('getQuote') + ->will($this->returnValue($this->quoteMock)); + + $this->eventManagerMock->expects($this->never())->method('dispatch')->with( + 'checkout_cart_product_add_after', + ['quote_item' => 1, 'product' => $product] + ); + $this->setExpectedException(\Magento\Framework\Exception\LocalizedException::class); + $this->cart->addProduct(4, 4); + } + + /** + * Test bad parameters on adding product for AddProduct + * + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testAddProductExceptionBadParams() + { + $product = $this->getMock( + \Magento\Catalog\Model\Product::class, + ['getWebsiteIds', 'getId', '__wakeup'], + [], + '', + false + ); + $product->expects($this->any()) + ->method('getId') + ->will($this->returnValue(4)); + $product->expects($this->any()) + ->method('getWebsiteIds') + ->will($this->returnValue([10])); + $this->productRepository->expects($this->any()) + ->method('getById') + ->will($this->returnValue($product)); + + $this->eventManagerMock->expects($this->never())->method('dispatch')->with( + 'checkout_cart_product_add_after', + ['quote_item' => 1, 'product' => $product] + ); + $this->setExpectedException(\Magento\Framework\Exception\LocalizedException::class); + $this->cart->addProduct(4, 'bad'); + } + + /** + * Data provider for testAddProduct + * + * @return array + */ + public function addProductDataProvider() + { + $obj = new ObjectManagerHelper($this) ; + $data = ['qty' => 5.5, 'sku' => 'prod']; + + return [ + 'prod_int_info_int' => [4, 4], + 'prod_int_info_array' => [ 4, $data], + 'prod_int_info_object' => [ + 4, + $obj->getObject( + \Magento\Framework\DataObject::class, + ['data' => $data] + ) + ], + 'prod_obj_info_int' => [null, 4], + 'prod_obj_info_array' => [ null, $data], + 'prod_obj_info_object' => [ + null, + $obj->getObject( + \Magento\Framework\DataObject::class, + ['data' => $data] + ) + ] + ]; + } } diff --git a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php index 093bbf4a5accc66bdd5c00fa4f30660409e09f79..76cbafb48ebd47e4fd83d01ef1361ef29df2f3be 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php @@ -42,6 +42,11 @@ class GuestPaymentInformationManagementTest extends \PHPUnit_Framework_TestCase */ protected $model; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -61,6 +66,7 @@ class GuestPaymentInformationManagementTest extends \PHPUnit_Framework_TestCase '', false ); + $this->loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); $this->model = $objectManager->getObject( \Magento\Checkout\Model\GuestPaymentInformationManagement::class, [ @@ -71,6 +77,7 @@ class GuestPaymentInformationManagementTest extends \PHPUnit_Framework_TestCase 'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock ] ); + $objectManager->setBackwardCompatibleProperty($this->model, 'logger', $this->loggerMock); } public function testSavePaymentInformationAndPlaceOrder() @@ -112,7 +119,7 @@ class GuestPaymentInformationManagementTest extends \PHPUnit_Framework_TestCase ->method('assign') ->with($cartId, $billingAddressMock); $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); - $exception = new CouldNotSaveException(__('DB exception')); + $exception = new \Exception(__('DB exception')); $this->cartManagementMock->expects($this->once())->method('placeOrder')->willThrowException($exception); $this->model->savePaymentInformationAndPlaceOrder($cartId, $email, $paymentMock, $billingAddressMock); @@ -161,4 +168,29 @@ class GuestPaymentInformationManagementTest extends \PHPUnit_Framework_TestCase $billingAddressMock->expects($this->once())->method('setEmail')->with($email); $this->assertTrue($this->model->savePaymentInformation($cartId, $email, $paymentMock)); } + + /** + * @expectedExceptionMessage DB exception + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + */ + public function testSavePaymentInformationAndPlaceOrderWithLocolizedException() + { + $cartId = 100; + $email = 'email@magento.com'; + $paymentMock = $this->getMock(\Magento\Quote\Api\Data\PaymentInterface::class); + $billingAddressMock = $this->getMock(\Magento\Quote\Api\Data\AddressInterface::class); + + $billingAddressMock->expects($this->once())->method('setEmail')->with($email)->willReturnSelf(); + + $this->billingAddressManagementMock->expects($this->once()) + ->method('assign') + ->with($cartId, $billingAddressMock); + $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); + $phrase = new \Magento\Framework\Phrase(__('DB exception')); + $exception = new \Magento\Framework\Exception\LocalizedException($phrase); + $this->loggerMock->expects($this->never())->method('critical'); + $this->cartManagementMock->expects($this->once())->method('placeOrder')->willThrowException($exception); + + $this->model->savePaymentInformationAndPlaceOrder($cartId, $email, $paymentMock, $billingAddressMock); + } } diff --git a/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php index 496054b2c7b1e0a0b9e60c119a8c25d322832a26..8da67a1fcb71512dd218a2c62d51b7922bb36c25 100644 --- a/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php +++ b/app/code/Magento/Checkout/Test/Unit/Model/PaymentInformationManagementTest.php @@ -29,6 +29,11 @@ class PaymentInformationManagementTest extends \PHPUnit_Framework_TestCase */ protected $model; + /** + * @var \PHPUnit_Framework_MockObject_MockObject + */ + private $loggerMock; + protected function setUp() { $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); @@ -40,6 +45,8 @@ class PaymentInformationManagementTest extends \PHPUnit_Framework_TestCase ); $this->cartManagementMock = $this->getMock(\Magento\Quote\Api\CartManagementInterface::class); + $this->loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class); + $this->model = $objectManager->getObject( \Magento\Checkout\Model\PaymentInformationManagement::class, [ @@ -48,6 +55,7 @@ class PaymentInformationManagementTest extends \PHPUnit_Framework_TestCase 'cartManagement' => $this->cartManagementMock ] ); + $objectManager->setBackwardCompatibleProperty($this->model, 'logger', $this->loggerMock); } public function testSavePaymentInformationAndPlaceOrder() @@ -83,7 +91,8 @@ class PaymentInformationManagementTest extends \PHPUnit_Framework_TestCase ->method('assign') ->with($cartId, $billingAddressMock); $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); - $exception = new CouldNotSaveException(__('DB exception')); + $exception = new \Exception(__('DB exception')); + $this->loggerMock->expects($this->once())->method('critical'); $this->cartManagementMock->expects($this->once())->method('placeOrder')->willThrowException($exception); $this->model->savePaymentInformationAndPlaceOrder($cartId, $paymentMock, $billingAddressMock); @@ -129,4 +138,26 @@ class PaymentInformationManagementTest extends \PHPUnit_Framework_TestCase $this->assertTrue($this->model->savePaymentInformation($cartId, $paymentMock)); } + + /** + * @expectedExceptionMessage DB exception + * @expectedException \Magento\Framework\Exception\CouldNotSaveException + */ + public function testSavePaymentInformationAndPlaceOrderWithLocolizedException() + { + $cartId = 100; + $paymentMock = $this->getMock(\Magento\Quote\Api\Data\PaymentInterface::class); + $billingAddressMock = $this->getMock(\Magento\Quote\Api\Data\AddressInterface::class); + + $this->billingAddressManagementMock->expects($this->once()) + ->method('assign') + ->with($cartId, $billingAddressMock); + $this->paymentMethodManagementMock->expects($this->once())->method('set')->with($cartId, $paymentMock); + $phrase = new \Magento\Framework\Phrase(__('DB exception')); + $exception = new \Magento\Framework\Exception\LocalizedException($phrase); + $this->loggerMock->expects($this->never())->method('critical'); + $this->cartManagementMock->expects($this->once())->method('placeOrder')->willThrowException($exception); + + $this->model->savePaymentInformationAndPlaceOrder($cartId, $paymentMock, $billingAddressMock); + } } diff --git a/app/code/Magento/Checkout/composer.json b/app/code/Magento/Checkout/composer.json index 5545e409b65abb3fb381e593c4ec34298dba6b80..60919d9fa2ca949817641c43328ad3be88edc8be 100644 --- a/app/code/Magento/Checkout/composer.json +++ b/app/code/Magento/Checkout/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-checkout", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-sales": "100.2.*", "magento/module-backend": "100.2.*", diff --git a/app/code/Magento/Checkout/etc/di.xml b/app/code/Magento/Checkout/etc/di.xml index c55598fdff5e792dd7404967d12f9fcc5e78e828..a2243b33a04edc0af4dc71ed79b802eac68f4cc2 100644 --- a/app/code/Magento/Checkout/etc/di.xml +++ b/app/code/Magento/Checkout/etc/di.xml @@ -26,4 +26,20 @@ <preference for="Magento\Checkout\Api\GuestTotalsInformationManagementInterface" type="Magento\Checkout\Model\GuestTotalsInformationManagement" /> <preference for="Magento\Checkout\Api\TotalsInformationManagementInterface" type="Magento\Checkout\Model\TotalsInformationManagement" /> <preference for="Magento\Checkout\Api\AgreementsValidatorInterface" type="Magento\Checkout\Model\AgreementsValidator" /> + <preference for="Magento\Checkout\Model\Cart\RequestInfoFilterInterface" + type="Magento\Checkout\Model\Cart\RequestInfoFilterComposite"/> + <type name="Magento\Checkout\Model\Cart\RequestInfoFilter"> + <arguments> + <argument name="filterList" xsi:type="array"> + <item name="form_key" xsi:type="string">form_key</item> + </argument> + </arguments> + </type> + <type name="Magento\Checkout\Model\Cart\RequestInfoFilterComposite"> + <arguments> + <argument name="filters" xsi:type="array"> + <item name="filter" xsi:type="object">Magento\Checkout\Model\Cart\RequestInfoFilter</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml index bccf81bcb6ee81c97bd6bef98d3c5f08ec33b9cc..6fb9058c3b7681c8905a40a5eace5d88d8566b6f 100644 --- a/app/code/Magento/Checkout/etc/frontend/di.xml +++ b/app/code/Magento/Checkout/etc/frontend/di.xml @@ -72,4 +72,12 @@ </argument> </arguments> </type> + <type name="Magento\Checkout\Model\Cart\RequestInfoFilter"> + <arguments> + <argument name="filterList" xsi:type="array"> + <item name="form_key" xsi:type="string">form_key</item> + <item name="custom_price" xsi:type="string">custom_price</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js b/app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js index 2d6fa460ef66d26d106ad9417ffdf8d3548a3d00..3ec34cd96574bc90e5b0f8bd3bf552a877444ab0 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/address-converter.js @@ -44,6 +44,13 @@ define( addressData.region.region_code = region['code']; addressData.region.region = region['name']; } + } else if ( + !addressData.region_id + && countryData()[addressData.country_id] + && countryData()[addressData.country_id]['regions'] + ) { + addressData.region.region_code = ''; + addressData.region.region = ''; } delete addressData.region_id; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/full-screen-loader.js b/app/code/Magento/Checkout/view/frontend/web/js/model/full-screen-loader.js index ca9d6493a16740f1a43555d84459df4403bd3c88..9005015d2460e12c5462551aecce97f1bd31c003 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/full-screen-loader.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/full-screen-loader.js @@ -24,7 +24,7 @@ define([ /** * Stop full page loader action * - * @param {Boolean} forceStop + * @param {Boolean} [forceStop] */ stopLoader: function (forceStop) { var $elem = $(containerId), diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js index bee4480b0dfb1c9db669d53f5091bb1d3564237a..9a3685b212b435e8c03adb7be2cd7a182e591b87 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/new-customer-address.js @@ -22,7 +22,7 @@ define([], function () { return { email: addressData.email, countryId: addressData['country_id'] || addressData.countryId || window.checkoutConfig.defaultCountryId, - regionId: regionId, + regionId: regionId || addressData.regionId, regionCode: (addressData.region) ? addressData.region.region_code : null, region: (addressData.region) ? addressData.region.region : null, customerId: addressData.customer_id, @@ -30,7 +30,7 @@ define([], function () { company: addressData.company, telephone: addressData.telephone, fax: addressData.fax, - postcode: addressData.postcode ? addressData.postcode : window.checkoutConfig.defaultPostcode, + postcode: addressData.postcode ? addressData.postcode : window.checkoutConfig.defaultPostcode || undefined, city: addressData.city, firstname: addressData.firstname, lastname: addressData.lastname, diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/payment/method-group.js b/app/code/Magento/Checkout/view/frontend/web/js/model/payment/method-group.js new file mode 100644 index 0000000000000000000000000000000000000000..4236a215d73596b918d782a2773698e5fa25234f --- /dev/null +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/payment/method-group.js @@ -0,0 +1,31 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'uiElement', + 'mage/translate' +], function (Element, $t) { + 'use strict'; + + var DEFAULT_GROUP_ALIAS = 'default'; + + return Element.extend({ + defaults: { + alias: DEFAULT_GROUP_ALIAS, + title: $t('Payment Method'), + sortOrder: 100, + displayArea: 'payment-methods-items-${ $.alias }' + }, + + /** + * Checks if group instance is default + * + * @returns {Boolean} + */ + isDefault: function () { + return this.alias === DEFAULT_GROUP_ALIAS; + } + }); +}); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js index 420d50b83478a91ab38b6a333a681f119b6db49e..c49960ecfb91dec735239c8bc1cc88f7ffebfab0 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/model/shipping-rates-validator.js @@ -176,7 +176,7 @@ define( address; if (this.validateAddressData(addressFlat)) { - addressFlat = $.extend(true, {}, quote.shippingAddress(), addressFlat); + addressFlat = uiRegistry.get('checkoutProvider').shippingAddress; address = addressConverter.formAddressDataToQuoteAddress(addressFlat); selectShippingAddress(address); } diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js b/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js index 18f6b8479c229326e28e9c02157d6a39e34625ee..918d305ee031b1d2c96eb6b246a765a16f6c8446 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/payment/list.js @@ -10,14 +10,22 @@ define([ 'Magento_Checkout/js/model/payment/method-list', 'Magento_Checkout/js/model/payment/renderer-list', 'uiLayout', - 'Magento_Checkout/js/model/checkout-data-resolver' -], function (_, ko, utils, Component, paymentMethods, rendererList, layout, checkoutDataResolver) { + 'Magento_Checkout/js/model/checkout-data-resolver', + 'mage/translate', + 'uiRegistry' +], function (_, ko, utils, Component, paymentMethods, rendererList, layout, checkoutDataResolver, $t, registry) { 'use strict'; return Component.extend({ defaults: { template: 'Magento_Checkout/payment-methods/list', - visible: paymentMethods().length > 0 + visible: paymentMethods().length > 0, + configDefaultGroup: { + name: 'methodGroup', + component: 'Magento_Checkout/js/model/payment/method-group' + }, + paymentGroupsList: [], + defaultGroupTitle: $t('Select a new payment method') }, /** @@ -26,7 +34,7 @@ define([ * @returns {Component} Chainable. */ initialize: function () { - this._super().initChildren(); + this._super().initDefaulGroup().initChildren(); paymentMethods.subscribe( function (changes) { checkoutDataResolver.resolvePaymentMethod(); @@ -47,6 +55,27 @@ define([ return this; }, + /** @inheritdoc */ + initObservable: function () { + this._super(). + observe(['paymentGroupsList']); + + return this; + }, + + /** + * Creates default group + * + * @returns {Component} Chainable. + */ + initDefaulGroup: function() { + layout([ + this.configDefaultGroup + ]); + + return this; + }, + /** * Create renders for child payment methods. * @@ -77,7 +106,7 @@ define([ rendererTemplate = { parent: '${ $.$data.parentName }', name: '${ $.$data.name }', - displayArea: 'payment-method-items', + displayArea: payment.displayArea, component: payment.component }; rendererComponent = utils.template(rendererTemplate, templateData); @@ -95,49 +124,105 @@ define([ * @param {Object} paymentMethodData */ createRenderer: function (paymentMethodData) { - var isRendererForMethod = false; + var isRendererForMethod = false, + currentGroup; + + registry.get(this.configDefaultGroup.name, function (defaultGroup) { + _.each(rendererList(), function (renderer) { - _.find(rendererList(), function (renderer) { + if (renderer.hasOwnProperty('typeComparatorCallback') && + typeof renderer.typeComparatorCallback == 'function' + ) { + isRendererForMethod = renderer.typeComparatorCallback(renderer.type, paymentMethodData.method); + } else { + isRendererForMethod = renderer.type === paymentMethodData.method; + } - if (renderer.hasOwnProperty('typeComparatorCallback') && - typeof renderer.typeComparatorCallback == 'function' - ) { - isRendererForMethod = renderer.typeComparatorCallback(renderer.type, paymentMethodData.method); - } else { - isRendererForMethod = renderer.type === paymentMethodData.method; - } + if (isRendererForMethod) { + currentGroup = renderer.group ? renderer.group : defaultGroup; - if (isRendererForMethod) { - layout( - [ + this.collectPaymentGroups(currentGroup); + + layout([ this.createComponent( { config: renderer.config, component: renderer.component, name: renderer.type, method: paymentMethodData.method, - item: paymentMethodData + item: paymentMethodData, + displayArea: currentGroup.displayArea } - ) - ] - ); - } + )]); + } + }.bind(this)); }.bind(this)); }, + /** + * Collects unique groups of available payment methods + * + * @param {Object} group + */ + collectPaymentGroups: function (group) { + var groupsList = this.paymentGroupsList(), + isGroupExists = _.some(groupsList, function (existsGroup) { + return existsGroup.alias === group.alias; + }); + + if (!isGroupExists) { + groupsList.push(group); + groupsList = _.sortBy(groupsList, function (existsGroup) { + return existsGroup.sortOrder; + }); + this.paymentGroupsList(groupsList); + } + }, + + /** + * Returns payment group title + * + * @param {Object} group + * @returns {String} + */ + getGroupTitle: function (group) { + var title = group().title; + + if (group().isDefault() && this.paymentGroupsList().length > 1) { + title = this.defaultGroupTitle; + } + + return title + ':'; + }, + + /** + * Checks if at least one payment method available + * + * @returns {String} + */ + isPaymentMethodsAvailable: function () { + return _.some(this.paymentGroupsList(), function (group) { + return this.getRegion(group.displayArea)().length; + }, this); + }, + /** * Remove view renderer. * * @param {String} paymentMethodCode */ removeRenderer: function (paymentMethodCode) { - var items = this.getRegion('payment-method-items'); + var items; + + _.each(this.paymentGroupsList(), function (group) { + items = this.getRegion(group.displayArea); - _.find(items(), function (value) { - if (value.item.method.indexOf(paymentMethodCode) === 0) { - value.disposeSubscriptions(); - value.destroy(); - } + _.find(items(), function (value) { + if (value.item.method.indexOf(paymentMethodCode) === 0) { + value.disposeSubscriptions(); + value.destroy(); + } + }); }, this); } }); diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js index ddb62ceb37b37ef6bc7735c78f81367c5c6d4343..8910e41731d11a9be99bf1777dc13e3550781976 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping.js @@ -179,7 +179,7 @@ define( newShippingAddress; this.source.set('params.invalid', false); - this.source.trigger('shippingAddress.data.validate'); + this.triggerShippingDataValidateEvent(); if (!this.source.get('params.invalid')) { addressData = this.source.get('shippingAddress'); @@ -254,12 +254,7 @@ define( if (this.isFormInline) { this.source.set('params.invalid', false); - this.source.trigger('shippingAddress.data.validate'); - - if (this.source.get('shippingAddress.custom_attributes')) { - this.source.trigger('shippingAddress.custom_attributes.data.validate'); - } - + this.triggerShippingDataValidateEvent(); if (emailValidationResult && this.source.get('params.invalid') || !quote.shippingMethod().method_code || @@ -304,6 +299,18 @@ define( } return true; + }, + + /** + * Trigger Shipping data Validate Event. + * + * @return {void} + */ + triggerShippingDataValidateEvent: function () { + this.source.trigger('shippingAddress.data.validate'); + if (this.source.get('shippingAddress.custom_attributes')) { + this.source.trigger('shippingAddress.custom_attributes.data.validate'); + } } }); } 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 f45115181fe4c628c88d3b5a26129629c21415b5..da625c51d4b7734ccc4808fd9161502e15acf1a9 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 @@ -13,6 +13,11 @@ <!-- 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 text: element[attribute].value --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> <button type="button" class="action action-edit-address" data-bind="visible: !isAddressSameAsShipping(), click: editAddress"> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/payment-methods/list.html b/app/code/Magento/Checkout/view/frontend/web/template/payment-methods/list.html index 66769f37386d6402c3d71d19f7f08ef6d88ca8b7..f6c41c62eeca62fcd89e0d85715863806d95e9f8 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/payment-methods/list.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/payment-methods/list.html @@ -4,9 +4,19 @@ * See COPYING.txt for license details. */ --> -<div class="items payment-methods"> - <!-- ko foreach: { data: getRegion('payment-method-items'), as: 'element'} --> - <!-- ko template: element.getTemplate() --><!-- /ko --> - <!-- /ko --> +<div if="isPaymentMethodsAvailable()" + class="items payment-methods"> + <div repeat="foreach: paymentGroupsList, item: '$group'" + class="payment-group"> + <div if="getRegion($group().displayArea)().length" + translate="getGroupTitle($group)" + class="step-title" + data-role="title"> + </div> + <each args="data: getRegion($group().displayArea), as: 'method'" render=""/> + </div> +</div> +<div ifnot="isPaymentMethodsAvailable()" + class="no-payments-block" + translate="'No Payment Methods'"> </div> -<!-- ko ifnot: getRegion('payment-method-items')().length > 0 --><div class="no-payments-block"><!-- ko i18n: 'No Payment Methods'--><!-- /ko --></div><!-- /ko --> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/payment.html b/app/code/Magento/Checkout/view/frontend/web/template/payment.html index 292ece611e6b664f04c4ee78a8ace01d03fb49f5..46467839da3fb08eb994d61b0d2aaa48912ada87 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/payment.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/payment.html @@ -5,7 +5,6 @@ */ --> <li id="payment" role="presentation" class="checkout-payment-method" data-bind="fadeVisible: isVisible"> - <div class="step-title" data-bind="i18n: title" data-role="title"></div> <div id="checkout-step-payment" class="step-content" data-role="content" 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 eeecbf346636613366db1f9d3fa33c4c7835b681..a2b83ead6b354451b99a4a1003a0f04df905dbbc 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 @@ -13,6 +13,11 @@ <!-- 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 text: element[attribute].value --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> <!-- ko if: (address().isEditable()) --> <button type="button" class="action edit-address-link" 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 f02eef5ded99ab86bb3a445f7de3355f810e94e8..440a2c7fc468fc493d10eb0cb57320c5593e1bf8 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 @@ -13,4 +13,9 @@ <!-- 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 text: element[attribute].value --><!-- /ko --> + <!-- /ko --> + <!-- /ko --> <!-- /ko --> diff --git a/app/code/Magento/CheckoutAgreements/composer.json b/app/code/Magento/CheckoutAgreements/composer.json index afb706a39a25ec97915abfe0bc6bfcb99c95f6d4..56a2b567745c2dd81fa3ca691309c4a76fe05aed 100644 --- a/app/code/Magento/CheckoutAgreements/composer.json +++ b/app/code/Magento/CheckoutAgreements/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-checkout-agreements", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-checkout": "100.2.*", "magento/module-quote": "100.2.*", "magento/module-store": "100.2.*", diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/web/js/model/agreement-validator.js b/app/code/Magento/CheckoutAgreements/view/frontend/web/js/model/agreement-validator.js index 3d030a62eb425cdb0365331c8fac5325a5dbc91d..2fa3c2cddd3d5af870f02db0e189cae8456c51aa 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/web/js/model/agreement-validator.js +++ b/app/code/Magento/CheckoutAgreements/view/frontend/web/js/model/agreement-validator.js @@ -12,9 +12,8 @@ define( function ($) { 'use strict'; var checkoutConfig = window.checkoutConfig, - agreementsConfig = checkoutConfig ? checkoutConfig.checkoutAgreements : {}; - - var agreementsInputPath = '.payment-method._active div.checkout-agreements input'; + agreementsConfig = checkoutConfig ? checkoutConfig.checkoutAgreements : {}, + agreementsInputPath = '.payment-method._active div.checkout-agreements input'; return { /** @@ -23,26 +22,11 @@ define( * @returns {boolean} */ validate: function() { - if (!agreementsConfig.isEnabled) { - return true; - } - - if ($(agreementsInputPath).length == 0) { + if (!agreementsConfig.isEnabled || $(agreementsInputPath).length == 0) { return true; } - return $('#co-payment-form').validate({ - errorClass: 'mage-error', - errorElement: 'div', - meta: 'validate', - errorPlacement: function (error, element) { - var errorPlacement = element; - if (element.is(':checkbox') || element.is(':radio')) { - errorPlacement = element.siblings('label').last(); - } - errorPlacement.after(error); - } - }).element(agreementsInputPath); + return $.validator.validateSingleElement(agreementsInputPath, {errorElement: 'div'}); } } } diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html b/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html index bb7d5de636ce5fd98b5f98943500910603d56267..7d8ef43084f3264c09bb65d2939f6660fa343f7f 100644 --- a/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html +++ b/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html @@ -8,14 +8,13 @@ <div class="checkout-agreements" data-bind="visible: isVisible"> <!-- ko foreach: agreements --> <!-- ko if: ($parent.isAgreementRequired($data)) --> - <div class="checkout-agreement"> - <input type="checkbox" + <div class="checkout-agreement required"> + <input type="checkbox" class="required-entry" data-bind="attr: { 'id': 'agreement_' + agreementId, 'name': 'agreement[' + agreementId + ']', 'value': agreementId - }" - data-validate="{required:true}" /> + }"/> <label data-bind="attr: {'for': 'agreement_' + agreementId}"> <button type="button" class="action action-show" diff --git a/app/code/Magento/Cms/Model/ResourceModel/Block/Collection.php b/app/code/Magento/Cms/Model/ResourceModel/Block/Collection.php index ec5a438247e5cddc205d2f49c8d49ea746943e34..1bd96eff4dceef562040df73978679cc236a00bb 100644 --- a/app/code/Magento/Cms/Model/ResourceModel/Block/Collection.php +++ b/app/code/Magento/Cms/Model/ResourceModel/Block/Collection.php @@ -41,6 +41,7 @@ class Collection extends AbstractCollection { $this->_init(\Magento\Cms\Model\Block::class, \Magento\Cms\Model\ResourceModel\Block::class); $this->_map['fields']['store'] = 'store_table.store_id'; + $this->_map['fields']['block_id'] = 'main_table.block_id'; } /** diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4e1de289e5ef0e004a34d70dd793c1715a8bf292 --- /dev/null +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/BlockActionsTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Cms\Test\Unit\Ui\Component\Listing\Column; + +use Magento\Cms\Ui\Component\Listing\Column\BlockActions; +use Magento\Framework\Escaper; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Framework\UrlInterface; +use Magento\Framework\View\Element\UiComponent\ContextInterface; +use Magento\Framework\View\Element\UiComponent\Processor; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * BlockActionsTest contains unit tests for \Magento\Cms\Ui\Component\Listing\Column\BlockActions class + */ +class BlockActionsTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var BlockActions + */ + private $blockActions; + + /** + * @var Escaper|MockObject + */ + private $escaper; + + /** + * @var UrlInterface|MockObject + */ + private $urlBuilder; + + protected function setUp() + { + $objectManager = new ObjectManager($this); + + $context = $this->getMock(ContextInterface::class); + + $processor = $this->getMockBuilder(Processor::class) + ->disableOriginalConstructor() + ->getMock(); + $context->expects(static::once()) + ->method('getProcessor') + ->willReturn($processor); + + $this->urlBuilder = $this->getMock(UrlInterface::class); + + $this->escaper = $this->getMockBuilder(Escaper::class) + ->disableOriginalConstructor() + ->setMethods(['escapeHtml']) + ->getMock(); + + $this->blockActions = $objectManager->getObject(BlockActions::class, [ + 'context' => $context, + 'urlBuilder' => $this->urlBuilder + ]); + + $objectManager->setBackwardCompatibleProperty($this->blockActions, 'escaper', $this->escaper); + } + + /** + * @covers \Magento\Cms\Ui\Component\Listing\Column\BlockActions::prepareDataSource + */ + public function testPrepareDataSource() + { + $blockId = 1; + $title = 'block title'; + $items = [ + 'data' => [ + 'items' => [ + [ + 'block_id' => $blockId, + 'title' => $title + ] + ] + ] + ]; + $name = 'item_name'; + $expectedItems = [ + [ + 'block_id' => $blockId, + 'title' => $title, + $name => [ + 'edit' => [ + 'href' => 'test/url/edit', + 'label' => __('Edit'), + ], + 'delete' => [ + 'href' => 'test/url/delete', + 'label' => __('Delete'), + 'confirm' => [ + 'title' => __('Delete %1', $title), + 'message' => __('Are you sure you wan\'t to delete a %1 record?', $title) + ], + ] + ], + ] + ]; + + $this->escaper->expects(static::once()) + ->method('escapeHtml') + ->with($title) + ->willReturn($title); + + $this->urlBuilder->expects(static::exactly(2)) + ->method('getUrl') + ->willReturnMap( + [ + [ + BlockActions::URL_PATH_EDIT, + [ + 'block_id' => $blockId + ], + 'test/url/edit', + ], + [ + BlockActions::URL_PATH_DELETE, + [ + 'block_id' => $blockId + ], + 'test/url/delete', + ], + ] + ); + + $this->blockActions->setData('name', $name); + + $actual = $this->blockActions->prepareDataSource($items); + static::assertEquals($expectedItems, $actual['data']['items']); + } +} diff --git a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php index 4b6ce1ae2ff42d39bf53a9e57bd3b9f6aa639f89..731c08fbc64dd59a967b4d74ea52ba188336d0a9 100644 --- a/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php +++ b/app/code/Magento/Cms/Test/Unit/Ui/Component/Listing/Column/PageActionsTest.php @@ -6,6 +6,7 @@ namespace Magento\Cms\Test\Unit\Ui\Component\Listing\Column; use Magento\Cms\Ui\Component\Listing\Column\PageActions; +use Magento\Framework\Escaper; class PageActionsTest extends \PHPUnit_Framework_TestCase { @@ -34,12 +35,20 @@ class PageActionsTest extends \PHPUnit_Framework_TestCase ] ); + $escaper = $this->getMockBuilder(Escaper::class) + ->disableOriginalConstructor() + ->setMethods(['escapeHtml']) + ->getMock(); + $objectManager->setBackwardCompatibleProperty($model, 'escaper', $escaper); + // Define test input and expectations + $title = 'page title'; $items = [ 'data' => [ 'items' => [ [ - 'page_id' => $pageId + 'page_id' => $pageId, + 'title' => $title ] ] ] @@ -48,6 +57,7 @@ class PageActionsTest extends \PHPUnit_Framework_TestCase $expectedItems = [ [ 'page_id' => $pageId, + 'title' => $title, $name => [ 'edit' => [ 'href' => 'test/url/edit', @@ -57,14 +67,19 @@ class PageActionsTest extends \PHPUnit_Framework_TestCase 'href' => 'test/url/delete', 'label' => __('Delete'), 'confirm' => [ - 'title' => __('Delete ${ $.$data.title }'), - 'message' => __('Are you sure you wan\'t to delete a ${ $.$data.title } record?') + 'title' => __('Delete %1', $title), + 'message' => __('Are you sure you wan\'t to delete a %1 record?', $title) ], ] ], ] ]; + $escaper->expects(static::once()) + ->method('escapeHtml') + ->with($title) + ->willReturn($title); + // Configure mocks and object data $urlBuilderMock->expects($this->any()) ->method('getUrl') diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php index e71352c465333396312cc0bd0242cbe45f62bd17..f0a1fe7981f8c3007055973eb4e296c23afe5ab8 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/BlockActions.php @@ -9,6 +9,8 @@ use Magento\Framework\UrlInterface; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Ui\Component\Listing\Columns\Column; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Escaper; /** * Class BlockActions @@ -27,6 +29,11 @@ class BlockActions extends Column */ protected $urlBuilder; + /** + * @var Escaper + */ + private $escaper; + /** * Constructor * @@ -47,10 +54,6 @@ class BlockActions extends Column parent::__construct($context, $uiComponentFactory, $components, $data); } - /** - * @param array $items - * @return array - */ /** * Prepare Data Source * @@ -62,6 +65,7 @@ class BlockActions extends Column if (isset($dataSource['data']['items'])) { foreach ($dataSource['data']['items'] as & $item) { if (isset($item['block_id'])) { + $title = $this->getEscaper()->escapeHtml($item['title']); $item[$this->getData('name')] = [ 'edit' => [ 'href' => $this->urlBuilder->getUrl( @@ -81,8 +85,8 @@ class BlockActions extends Column ), 'label' => __('Delete'), 'confirm' => [ - 'title' => __('Delete "${ $.$data.title }"'), - 'message' => __('Are you sure you wan\'t to delete a "${ $.$data.title }" record?') + 'title' => __('Delete %1', $title), + 'message' => __('Are you sure you wan\'t to delete a %1 record?', $title) ] ] ]; @@ -92,4 +96,17 @@ class BlockActions extends Column return $dataSource; } + + /** + * Get instance of escaper + * @return Escaper + * @deprecated + */ + private function getEscaper() + { + if (!$this->escaper) { + $this->escaper = ObjectManager::getInstance()->get(Escaper::class); + } + return $this->escaper; + } } diff --git a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php index fe85fd0ef314070c5c6cdb6f34435e72af3e152b..f23afbffa792e6b5b92166b5268e3942735bd21f 100644 --- a/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php +++ b/app/code/Magento/Cms/Ui/Component/Listing/Column/PageActions.php @@ -5,11 +5,13 @@ */ namespace Magento\Cms\Ui\Component\Listing\Column; +use Magento\Cms\Block\Adminhtml\Page\Grid\Renderer\Action\UrlBuilder; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Escaper; +use Magento\Framework\UrlInterface; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Ui\Component\Listing\Columns\Column; -use Magento\Cms\Block\Adminhtml\Page\Grid\Renderer\Action\UrlBuilder; -use Magento\Framework\UrlInterface; /** * Class PageActions @@ -31,6 +33,11 @@ class PageActions extends Column */ private $editUrl; + /** + * @var Escaper + */ + private $escaper; + /** * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory @@ -71,12 +78,13 @@ class PageActions extends Column 'href' => $this->urlBuilder->getUrl($this->editUrl, ['page_id' => $item['page_id']]), 'label' => __('Edit') ]; + $title = $this->getEscaper()->escapeHtml($item['title']); $item[$name]['delete'] = [ 'href' => $this->urlBuilder->getUrl(self::CMS_URL_PATH_DELETE, ['page_id' => $item['page_id']]), 'label' => __('Delete'), 'confirm' => [ - 'title' => __('Delete ${ $.$data.title }'), - 'message' => __('Are you sure you wan\'t to delete a ${ $.$data.title } record?') + 'title' => __('Delete %1', $title), + 'message' => __('Are you sure you wan\'t to delete a %1 record?', $title) ] ]; } @@ -95,4 +103,17 @@ class PageActions extends Column return $dataSource; } + + /** + * Get instance of escaper + * @return Escaper + * @deprecated + */ + private function getEscaper() + { + if (!$this->escaper) { + $this->escaper = ObjectManager::getInstance()->get(Escaper::class); + } + return $this->escaper; + } } diff --git a/app/code/Magento/Cms/composer.json b/app/code/Magento/Cms/composer.json index 66f6da042e6c9a77e25eb5c9e8f1595c16e3e5f6..7f2ed4d7a7dcdc59916c34c796f7f3fc05ec6cc9 100644 --- a/app/code/Magento/Cms/composer.json +++ b/app/code/Magento/Cms/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-cms", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-theme": "100.2.*", "magento/module-widget": "100.2.*", diff --git a/app/code/Magento/Cms/i18n/en_US.csv b/app/code/Magento/Cms/i18n/en_US.csv index 6f3a2955a5c65b5cf1760519741fd60d49133123..9a9f6ac4c6888e386d44c679c94a87533844342d 100644 --- a/app/code/Magento/Cms/i18n/en_US.csv +++ b/app/code/Magento/Cms/i18n/en_US.csv @@ -84,10 +84,8 @@ Exception,Exception "All Store Views","All Store Views" Edit,Edit Delete,Delete -"Delete ${ $.$data.title }","Delete ${ $.$data.title }" -"Are you sure you wan\'t to delete a ${ $.$data.title } record?","Are you sure you wan\'t to delete a ${ $.$data.title } record?" -"Delete ""${ $.$data.title }""","Delete ""${ $.$data.title }""" -"Are you sure you wan\'t to delete a ""${ $.$data.title }"" record?","Are you sure you wan\'t to delete a ""${ $.$data.title }"" record?" +"Delete %1","Delete %1" +"Are you sure you wan\'t to delete a %1 record?","Are you sure you wan\'t to delete a %1 record?" View,View px.,px. "No files found","No files found" diff --git a/app/code/Magento/CmsUrlRewrite/composer.json b/app/code/Magento/CmsUrlRewrite/composer.json index 1ae1efd9350a760846e76fcac8939ef574194361..1d2e70c74eb4f9fd0876f92c37b30b5e9387d2cd 100644 --- a/app/code/Magento/CmsUrlRewrite/composer.json +++ b/app/code/Magento/CmsUrlRewrite/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-cms-url-rewrite", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-cms": "101.1.*", "magento/module-url-rewrite": "100.2.*", diff --git a/app/code/Magento/Config/Model/Config/Backend/Baseurl.php b/app/code/Magento/Config/Model/Config/Backend/Baseurl.php index 09fd99773813117a71b43240eaafc5b96bf967ca..f08c2f8e04434c7a26645669ff3a4c1c8d1840c7 100644 --- a/app/code/Magento/Config/Model/Config/Backend/Baseurl.php +++ b/app/code/Magento/Config/Model/Config/Backend/Baseurl.php @@ -5,6 +5,9 @@ */ namespace Magento\Config\Model\Config\Backend; +use Magento\Framework\Validator\Url as UrlValidator; +use Magento\Framework\App\ObjectManager; + class Baseurl extends \Magento\Framework\App\Config\Value { /** @@ -12,6 +15,11 @@ class Baseurl extends \Magento\Framework\App\Config\Value */ protected $_mergeService; + /** + * @var UrlValidator + */ + private $urlValidator; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -193,8 +201,7 @@ class Baseurl extends \Magento\Framework\App\Config\Value */ private function _isFullyQualifiedUrl($value) { - $url = parse_url($value); - return isset($url['scheme']) && isset($url['host']) && preg_match('/\/$/', $value); + return preg_match('/\/$/', $value) && $this->getUrlValidator()->isValid($value, ['http', 'https']); } /** @@ -216,4 +223,18 @@ class Baseurl extends \Magento\Framework\App\Config\Value } return parent::afterSave(); } + + /** + * Get URL Validator + * + * @deprecated + * @return UrlValidator + */ + private function getUrlValidator() + { + if (!$this->urlValidator) { + $this->urlValidator = ObjectManager::getInstance()->get(UrlValidator::class); + } + return $this->urlValidator; + } } diff --git a/app/code/Magento/Config/composer.json b/app/code/Magento/Config/composer.json index bdd1a9c14ae6894d052f6e7e2994f2c87c6554b0..b14dd825b0d12d819fd511222e80de12a7cf8c3a 100644 --- a/app/code/Magento/Config/composer.json +++ b/app/code/Magento/Config/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-config", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*", "magento/module-store": "100.2.*", "magento/module-cron": "100.2.*", diff --git a/app/code/Magento/ConfigurableImportExport/composer.json b/app/code/Magento/ConfigurableImportExport/composer.json index 6c249134d28626b4c8846fd7491cf25b2e278638..d292e73198435431015dff6bcfe1a69d516debc9 100644 --- a/app/code/Magento/ConfigurableImportExport/composer.json +++ b/app/code/Magento/ConfigurableImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-configurable-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-catalog-import-export": "100.2.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/ConfigurableProduct/Block/Plugin/Product/Media/Gallery.php b/app/code/Magento/ConfigurableProduct/Block/Plugin/Product/Media/Gallery.php deleted file mode 100644 index a4390bf94865084fd27d401e47982c3bdb3cabbf..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Block/Plugin/Product/Media/Gallery.php +++ /dev/null @@ -1,102 +0,0 @@ -<?php -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -namespace Magento\ConfigurableProduct\Block\Plugin\Product\Media; - -use Magento\ConfigurableProduct\Model\Product\Type\Configurable; - -/** - * Class Gallery - */ -class Gallery extends \Magento\Catalog\Block\Product\View\AbstractView -{ - /** - * @var \Magento\Catalog\Model\Product\Gallery\ReadHandler - */ - private $productGalleryReadHandler; - - /** - * @var \Magento\Framework\Json\EncoderInterface - */ - private $jsonEncoder; - - /** - * @var \Magento\Framework\Json\DecoderInterface - */ - private $jsonDecoder; - - /** - * Gallery constructor. - * @param \Magento\Catalog\Block\Product\Context $context - * @param \Magento\Framework\Stdlib\ArrayUtils $arrayUtils - * @param \Magento\Catalog\Model\Product\Gallery\ReadHandler $productGalleryReadHandler - * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder - * @param \Magento\Framework\Json\DecoderInterface $jsonDecoder - * @param array $data - */ - public function __construct( - \Magento\Catalog\Block\Product\Context $context, - \Magento\Framework\Stdlib\ArrayUtils $arrayUtils, - \Magento\Catalog\Model\Product\Gallery\ReadHandler $productGalleryReadHandler, - \Magento\Framework\Json\EncoderInterface $jsonEncoder, - \Magento\Framework\Json\DecoderInterface $jsonDecoder, - array $data = [] - ) { - $this->productGalleryReadHandler = $productGalleryReadHandler; - $this->jsonEncoder = $jsonEncoder; - $this->jsonDecoder = $jsonDecoder; - parent::__construct($context, $arrayUtils, $data); - } - - /** - * @param \Magento\Catalog\Block\Product\View\Gallery $subject - * @param string $result - * @return string - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function afterGetOptionsMediaGalleryDataJson( - \Magento\Catalog\Block\Product\View\Gallery $subject, - $result - ) { - $result = $this->jsonDecoder->decode($result); - if ($this->getProduct()->getTypeId() == 'configurable') { - /** @var Configurable $productType */ - $productType = $this->getProduct()->getTypeInstance(); - $products = $productType->getUsedProducts($this->getProduct()); - $attributes = $productType->getConfigurableAttributesAsArray($this->getProduct()); - /** @var \Magento\Catalog\Model\Product $product */ - foreach ($attributes as $attribute) { - foreach ($products as $product) { - $attributeValue = $product->getData($attribute['attribute_code']); - if ($attributeValue) { - $key = $attribute['attribute_code'] . '_' . $attributeValue; - $result[$key] = $this->getProductGallery($product); - } - } - } - } - return $this->jsonEncoder->encode($result); - } - - /** - * @param \Magento\Catalog\Model\Product $product - * @return array - */ - private function getProductGallery($product) - { - $result = []; - $this->productGalleryReadHandler->execute($product); - $images = $product->getMediaGalleryImages(); - foreach ($images as $image) { - $result[] = [ - 'mediaType' => $image->getMediaType(), - 'videoUrl' => $image->getVideoUrl(), - 'isBase' => $product->getImage() == $image->getFile(), - ]; - } - return $result; - } -} diff --git a/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php b/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php index 0fdfbd3bfa172ee597414ed3ab4c46bbac2a9549..5575d6f7ef56fa4f7f2fed433299c01eaa4ba98d 100644 --- a/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php +++ b/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php @@ -32,6 +32,16 @@ class LinkManagement implements \Magento\ConfigurableProduct\Api\LinkManagementI */ private $dataObjectHelper; + /** + * @var \Magento\ConfigurableProduct\Helper\Product\Options\Factory; + */ + private $optionsFactory; + + /** + * @var \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory + */ + private $attributeFactory; + /** * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository * @param \Magento\Catalog\Api\Data\ProductInterfaceFactory $productFactory @@ -102,9 +112,28 @@ class LinkManagement implements \Magento\ConfigurableProduct\Api\LinkManagementI throw new StateException(__('Product has been already attached')); } + $configurableProductOptions = $product->getExtensionAttributes()->getConfigurableProductOptions(); + if (empty($configurableProductOptions)) { + throw new StateException(__('Parent product does not have configurable product options')); + } + + $attributeIds = []; + foreach ($configurableProductOptions as $configurableProductOption) { + $attributeCode = $configurableProductOption->getProductAttribute()->getAttributeCode(); + if (!$child->getData($attributeCode)) { + throw new StateException(__('Child product does not have attribute value %1', $attributeCode)); + } + $attributeIds[] = $configurableProductOption->getAttributeId(); + } + $configurableOptionData = $this->getConfigurableAttributesData($attributeIds); + + /** @var \Magento\ConfigurableProduct\Helper\Product\Options\Factory $optionFactory */ + $optionFactory = $this->getOptionsFactory(); + $options = $optionFactory->create($configurableOptionData); $childrenIds[] = $child->getId(); + $product->getExtensionAttributes()->setConfigurableProductOptions($options); $product->getExtensionAttributes()->setConfigurableProductLinks($childrenIds); - $product->save(); + $this->productRepository->save($product); return true; } @@ -133,7 +162,75 @@ class LinkManagement implements \Magento\ConfigurableProduct\Api\LinkManagementI throw new NoSuchEntityException(__('Requested option doesn\'t exist')); } $product->getExtensionAttributes()->setConfigurableProductLinks($ids); - $product->save(); + $this->productRepository->save($product); return true; } + + /** + * Get Options Factory + * + * @return \Magento\ConfigurableProduct\Helper\Product\Options\Factory + * + * @deprecated + */ + private function getOptionsFactory() + { + if (!$this->optionsFactory) { + $this->optionsFactory = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\ConfigurableProduct\Helper\Product\Options\Factory::class); + } + return $this->optionsFactory; + } + + /** + * Get Attribute Factory + * + * @return \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory + * + * @deprecated + */ + private function getAttributeFactory() + { + if (!$this->attributeFactory) { + $this->attributeFactory = \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory::class); + } + return $this->attributeFactory; + } + + /** + * Get Configurable Attribute Data + * + * @param int[] $attributeIds + * @return array + */ + private function getConfigurableAttributesData($attributeIds) + { + $configurableAttributesData = []; + $attributeValues = []; + $attributes = $this->getAttributeFactory()->create() + ->getCollection() + ->addFieldToFilter('attribute_id', $attributeIds) + ->getItems(); + foreach ($attributes as $attribute) { + foreach ($attribute->getOptions() as $option) { + if ($option->getValue()) { + $attributeValues[] = [ + 'label' => $option->getLabel(), + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + } + } + $configurableAttributesData[] = + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'values' => $attributeValues, + ]; + } + + return $configurableAttributesData; + } } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index 4e6fa8ae5210d77726b393047a7433ca03d69c7d..fbb69798ff94fbdf8caafc7486e564da662cf655 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -8,6 +8,7 @@ namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status as ProductStatus; class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice { @@ -166,6 +167,9 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\ $this->_prepareConfigurableOptionAggregateTable(); $this->_prepareConfigurableOptionPriceTable(); + $statusAttribute = $this->_getAttribute(ProductInterface::STATUS); + $linkField = $metadata->getLinkField(); + $select = $connection->select()->from( ['i' => $this->_getDefaultFinalPriceTable()], [] @@ -175,7 +179,7 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\ ['parent_id' => 'e.entity_id'] )->join( ['l' => $this->getTable('catalog_product_super_link')], - 'l.parent_id = e.' . $metadata->getLinkField(), + 'l.parent_id = e.' . $linkField, ['product_id'] )->columns( ['customer_group_id', 'website_id'], @@ -186,11 +190,21 @@ class Configurable extends \Magento\Catalog\Model\ResourceModel\Product\Indexer\ [] )->where( 'le.required_options=0' + )->join( + ['product_status' => $this->getTable($statusAttribute->getBackend()->getTable())], + sprintf( + 'le.%1$s = product_status.%1$s AND product_status.attribute_id = %2$s', + $linkField, + $statusAttribute->getAttributeId() + ), + [] + )->where( + 'product_status.value=' . ProductStatus::STATUS_ENABLED )->group( - ['parent_id', 'i.customer_group_id', 'i.website_id', 'l.product_id'] + ['e.entity_id', 'i.customer_group_id', 'i.website_id', 'l.product_id'] ); - $priceColumn = $this->_addAttributeToSelect($select, 'price', 'l.product_id', 0, null, true); - $tierPriceColumn = $connection->getCheckSql("MIN(i.tier_price) IS NOT NULL", "i.tier_price", 'NULL'); + $priceColumn = $this->_addAttributeToSelect($select, 'price', 'le.' . $linkField, 0, null, true); + $tierPriceColumn = $connection->getIfNullSql('MIN(i.tier_price)', 'NULL'); $select->columns( ['price' => $priceColumn, 'tier_price' => $tierPriceColumn] diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php index dcb75fe725dc20d74aa245831e8046dbd9665949..872538d9babab02aec00763ee317298f9c93697e 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProvider.php @@ -18,26 +18,6 @@ class ConfigurableOptionsProvider implements ConfigurableOptionsProviderInterfac /** @var Configurable */ private $configurable; - /** - * @var RequestSafetyInterface - */ - private $requestSafety; - - /** - * @var ResourceConnection - */ - private $resource; - - /** - * @var LinkedProductSelectBuilderInterface - */ - private $linkedProductSelectBuilder; - - /** - * @var CollectionFactory - */ - private $collectionFactory; - /** * @var ProductInterface[] */ @@ -49,6 +29,7 @@ class ConfigurableOptionsProvider implements ConfigurableOptionsProviderInterfac * @param LinkedProductSelectBuilderInterface $linkedProductSelectBuilder * @param CollectionFactory $collectionFactory * @param RequestSafetyInterface $requestSafety + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( Configurable $configurable, @@ -58,10 +39,6 @@ class ConfigurableOptionsProvider implements ConfigurableOptionsProviderInterfac RequestSafetyInterface $requestSafety ) { $this->configurable = $configurable; - $this->resource = $resourceConnection; - $this->linkedProductSelectBuilder = $linkedProductSelectBuilder; - $this->collectionFactory = $collectionFactory; - $this->requestSafety = $requestSafety; } /** @@ -70,17 +47,7 @@ class ConfigurableOptionsProvider implements ConfigurableOptionsProviderInterfac public function getProducts(ProductInterface $product) { if (!isset($this->products[$product->getId()])) { - if ($this->requestSafety->isSafeMethod()) { - $productIds = $this->resource->getConnection()->fetchCol( - '(' . implode(') UNION (', $this->linkedProductSelectBuilder->build($product->getId())) . ')' - ); - - $this->products[$product->getId()] = $this->collectionFactory->create() - ->addAttributeToSelect(['price', 'special_price']) - ->addIdFilter($productIds); - } else { - $this->products[$product->getId()] = $this->configurable->getUsedProducts($product); - } + $this->products[$product->getId()] = $this->configurable->getUsedProducts($product); } return $this->products[$product->getId()]; } diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProviderInterface.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProviderInterface.php index c7ac9446f42e306b78badec5f4b0825ce8c6f6c1..c0f2c218cc77c80a8e6785aba97431baea793975 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProviderInterface.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableOptionsProviderInterface.php @@ -10,6 +10,7 @@ use Magento\Catalog\Api\Data\ProductInterface; /** * Provide configurable sub-products for price calculation + * @api */ interface ConfigurableOptionsProviderInterface { diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php index 75d08b5aa419bdec6d9db0c3112f2aa1b7e836a6..68e82ed76a23fbdda404c32eb7406d3117b3cd0e 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurablePriceResolver.php @@ -6,7 +6,6 @@ namespace Magento\ConfigurableProduct\Pricing\Price; -use Magento\Catalog\Model\Product; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\PriceCurrencyInterface; @@ -29,23 +28,27 @@ class ConfigurablePriceResolver implements PriceResolverInterface protected $configurable; /** - * @var ConfigurableOptionsProviderInterface + * @var LowestPriceOptionsProviderInterface */ - private $configurableOptionsProvider; + private $lowestPriceOptionsProvider; /** * @param PriceResolverInterface $priceResolver * @param Configurable $configurable * @param PriceCurrencyInterface $priceCurrency + * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider */ public function __construct( PriceResolverInterface $priceResolver, Configurable $configurable, - PriceCurrencyInterface $priceCurrency + PriceCurrencyInterface $priceCurrency, + LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider = null ) { $this->priceResolver = $priceResolver; $this->configurable = $configurable; $this->priceCurrency = $priceCurrency; + $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?: + ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class); } /** @@ -57,7 +60,7 @@ class ConfigurablePriceResolver implements PriceResolverInterface { $price = null; - foreach ($this->getConfigurableOptionsProvider()->getProducts($product) as $subProduct) { + foreach ($this->lowestPriceOptionsProvider->getProducts($product) as $subProduct) { $productPrice = $this->priceResolver->resolvePrice($subProduct); $price = $price ? min($price, $productPrice) : $productPrice; } @@ -69,17 +72,4 @@ class ConfigurablePriceResolver implements PriceResolverInterface return (float)$price; } - - /** - * @return \Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface - * @deprecated - */ - private function getConfigurableOptionsProvider() - { - if (null === $this->configurableOptionsProvider) { - $this->configurableOptionsProvider = ObjectManager::getInstance() - ->get(ConfigurableOptionsProviderInterface::class); - } - return $this->configurableOptionsProvider; - } } diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php index 14b788258ab75502183aaf0b3f842b1943fcec2d..7a710e2caacc96f0a20eae124d973de9fa6a4c49 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/ConfigurableRegularPrice.php @@ -44,22 +44,31 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu */ private $configurableOptionsProvider; + /** + * @var LowestPriceOptionsProviderInterface + */ + private $lowestPriceOptionsProvider; + /** * @param \Magento\Framework\Pricing\SaleableInterface $saleableItem * @param float $quantity * @param \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator * @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency * @param PriceResolverInterface $priceResolver + * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider */ public function __construct( \Magento\Framework\Pricing\SaleableInterface $saleableItem, $quantity, \Magento\Framework\Pricing\Adjustment\CalculatorInterface $calculator, \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency, - PriceResolverInterface $priceResolver + PriceResolverInterface $priceResolver, + LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider = null ) { parent::__construct($saleableItem, $quantity, $calculator, $priceCurrency); $this->priceResolver = $priceResolver; + $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?: + ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class); } /** @@ -88,7 +97,6 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu public function getMaxRegularAmount() { if (null === $this->maxRegularAmount) { - $this->maxRegularAmount = $this->doGetMaxRegularAmount(); $this->maxRegularAmount = $this->doGetMaxRegularAmount() ?: false; } return $this->maxRegularAmount; @@ -96,7 +104,7 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu } /** - * Get max regular amount. Template method + * Get max regular amount * * @return \Magento\Framework\Pricing\Amount\AmountInterface */ @@ -124,14 +132,14 @@ class ConfigurableRegularPrice extends AbstractPrice implements ConfigurableRegu } /** - * Get min regular amount. Template method + * Get min regular amount * * @return \Magento\Framework\Pricing\Amount\AmountInterface */ protected function doGetMinRegularAmount() { $minAmount = null; - foreach ($this->getUsedProducts() as $product) { + foreach ($this->lowestPriceOptionsProvider->getProducts($this->product) as $product) { $childPriceAmount = $product->getPriceInfo()->getPrice(self::PRICE_CODE)->getAmount(); if (!$minAmount || ($childPriceAmount->getValue() < $minAmount->getValue())) { $minAmount = $childPriceAmount; diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php new file mode 100644 index 0000000000000000000000000000000000000000..1b758866100eb8f52faa371e4bc1695ce385703f --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProvider.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Pricing\Price; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; + +/** + * Retrieve list of products where each product contains lower price than others at least for one possible price type + */ +class LowestPriceOptionsProvider implements LowestPriceOptionsProviderInterface +{ + /** + * @var ResourceConnection + */ + private $resource; + + /** + * @var LinkedProductSelectBuilderInterface + */ + private $linkedProductSelectBuilder; + + /** + * @var CollectionFactory + */ + private $collectionFactory; + + /** + * @param ResourceConnection $resourceConnection + * @param LinkedProductSelectBuilderInterface $linkedProductSelectBuilder + * @param CollectionFactory $collectionFactory + */ + public function __construct( + ResourceConnection $resourceConnection, + LinkedProductSelectBuilderInterface $linkedProductSelectBuilder, + CollectionFactory $collectionFactory + ) { + $this->resource = $resourceConnection; + $this->linkedProductSelectBuilder = $linkedProductSelectBuilder; + $this->collectionFactory = $collectionFactory; + } + + /** + * {@inheritdoc} + */ + public function getProducts(ProductInterface $product) + { + $productIds = $this->resource->getConnection()->fetchCol( + '(' . implode(') UNION (', $this->linkedProductSelectBuilder->build($product->getId())) . ')' + ); + + $lowestPriceChildProducts = $this->collectionFactory->create() + ->addAttributeToSelect(['price', 'special_price']) + ->addIdFilter($productIds) + ->getItems(); + return $lowestPriceChildProducts; + } +} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProviderInterface.php b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProviderInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..c0989a929be55ce46bdaa3451ba88a8a3ae8272a --- /dev/null +++ b/app/code/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionsProviderInterface.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Pricing\Price; + +use Magento\Catalog\Api\Data\ProductInterface; + +/** + * Retrieve list of products where each product contains lower price than others at least for one possible price type + * @api + */ +interface LowestPriceOptionsProviderInterface +{ + /** + * @param ProductInterface $product + * @return \Magento\Catalog\Api\Data\ProductInterface[] + */ + public function getProducts(\Magento\Catalog\Api\Data\ProductInterface $product); +} diff --git a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php index 16a296a3554548d31f1692f397cd0124e272a1af..19bf0eee888f181902824505a2808f8ae5106c15 100644 --- a/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php +++ b/app/code/Magento/ConfigurableProduct/Pricing/Render/FinalPriceBox.php @@ -8,6 +8,8 @@ namespace Magento\ConfigurableProduct\Pricing\Render; use Magento\Catalog\Pricing\Price\FinalPrice; use Magento\Catalog\Pricing\Price\RegularPrice; use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface; +use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Pricing\Price\PriceInterface; use Magento\Framework\Pricing\Render\RendererPool; use Magento\Framework\Pricing\SaleableInterface; @@ -16,9 +18,9 @@ use Magento\Framework\View\Element\Template\Context; class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox { /** - * @var ConfigurableOptionsProviderInterface + * @var LowestPriceOptionsProviderInterface */ - private $configurableOptionsProvider; + private $lowestPriceOptionsProvider; /** * @param Context $context @@ -27,6 +29,8 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox * @param RendererPool $rendererPool * @param ConfigurableOptionsProviderInterface $configurableOptionsProvider * @param array $data + * @param LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider + * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function __construct( Context $context, @@ -34,10 +38,12 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox PriceInterface $price, RendererPool $rendererPool, ConfigurableOptionsProviderInterface $configurableOptionsProvider, - array $data = [] + array $data = [], + LowestPriceOptionsProviderInterface $lowestPriceOptionsProvider = null ) { - $this->configurableOptionsProvider = $configurableOptionsProvider; parent::__construct($context, $saleableItem, $price, $rendererPool, $data); + $this->lowestPriceOptionsProvider = $lowestPriceOptionsProvider ?: + ObjectManager::getInstance()->get(LowestPriceOptionsProviderInterface::class); } /** @@ -48,7 +54,7 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox public function hasSpecialPrice() { $product = $this->getSaleableItem(); - foreach ($this->configurableOptionsProvider->getProducts($product) as $subProduct) { + foreach ($this->lowestPriceOptionsProvider->getProducts($product) as $subProduct) { $regularPrice = $subProduct->getPriceInfo()->getPrice(RegularPrice::PRICE_CODE)->getValue(); $finalPrice = $subProduct->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue(); if ($finalPrice < $regularPrice) { diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Plugin/Product/Media/GalleryTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Plugin/Product/Media/GalleryTest.php deleted file mode 100644 index 60aa30c8d40784796192d8c1b7a2ea871e505a25..0000000000000000000000000000000000000000 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Plugin/Product/Media/GalleryTest.php +++ /dev/null @@ -1,111 +0,0 @@ -<?php -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -namespace Magento\ConfigurableProduct\Test\Unit\Block\Plugin\Product\Media; - -/** - * Class GalleryTest - */ -class GalleryTest extends \PHPUnit_Framework_TestCase -{ - /** - * @var \Magento\ConfigurableProduct\Block\Plugin\Product\Media\Gallery - */ - private $plugin; - - /** - * @var \Magento\Framework\Json\EncoderInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $jsonEncoder; - - /** - * @var \Magento\Framework\Json\DecoderInterface|\PHPUnit_Framework_MockObject_MockObject - */ - private $jsonDecoder; - - /** - * @var \Magento\Catalog\Model\Product\Gallery\ReadHandler - */ - private $galleryHandler; - - protected function setUp() - { - $helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->galleryHandler = $this->getMockBuilder(\Magento\Catalog\Model\Product\Gallery\ReadHandler::class) - ->disableOriginalConstructor() - ->setMethods(['execute']) - ->getMock(); - - $this->jsonEncoder = $this->getMock(\Magento\Framework\Json\EncoderInterface::class); - $this->jsonDecoder = $this->getMock(\Magento\Framework\Json\DecoderInterface::class); - - $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->setMethods(['getTypeId', 'getTypeInstance']) - ->disableOriginalConstructor() - ->getMock(); - - $variationProduct = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) - ->setMethods(['setMediaGalleryEntries', 'getSku', 'getMediaGalleryImages', 'getImage', 'getData']) - ->disableOriginalConstructor() - ->getMock(); - $image = new \Magento\Framework\DataObject( - ['media_type' => 'type', 'video_url' => 'url', 'file' => 'image.jpg'] - ); - $variationProduct->expects($this->any())->method('setMediaGalleryEntries')->willReturn([]); - $variationProduct->expects($this->any())->method('getSku')->willReturn('sku'); - $variationProduct->expects($this->any())->method('getMediaGalleryImages')->willReturn([$image]); - $variationProduct->expects($this->any())->method('getImage')->willReturn('image.jpg'); - $variationProduct->expects($this->any())->method('getData')->with('configurable_attribute')->willReturn(1); - - $this->galleryHandler->expects($this->once())->method('execute')->with($variationProduct); - - $configurableType = $this->getMockBuilder(\Magento\ConfigurableProduct\Model\Product\Type\Configurable::class) - ->disableOriginalConstructor() - ->setMethods(['getUsedProducts', 'getConfigurableAttributesAsArray']) - ->getMock(); - $configurableType->expects($this->any())->method('getUsedProducts')->with($productMock) - ->willReturn([$variationProduct]); - $configurableType->expects($this->any())->method('getConfigurableAttributesAsArray')->with($productMock) - ->willReturn([['attribute_code' => 'configurable_attribute']]); - - $productMock->expects($this->any())->method('getTypeId')->willReturn('configurable'); - $productMock->expects($this->any())->method('getTypeInstance')->willReturn($configurableType); - - $this->plugin = $helper->getObject( - \Magento\ConfigurableProduct\Block\Plugin\Product\Media\Gallery::class, - [ - 'productGalleryReadHandler' => $this->galleryHandler, - 'jsonEncoder' => $this->jsonEncoder, - 'jsonDecoder' => $this->jsonDecoder - ] - ); - $this->plugin->setData('product', $productMock); - } - - public function testAfterGetOptions() - { - $resultJson = '[]'; - $this->jsonDecoder->expects($this->once())->method('decode')->with('[]')->willReturn([]); - $expected = [ - 'configurable_attribute_1' => [ - [ - 'mediaType' => 'type', - 'videoUrl' => 'url', - 'isBase' => true - ] - ] - ]; - $this->jsonEncoder->expects($this->any())->method('encode')->with($expected) - ->willReturn(json_encode($expected)); - - $blockMock = $this->getMockBuilder(\Magento\ProductVideo\Block\Product\View\Gallery::class) - ->disableOriginalConstructor() - ->getMock(); - - $result = $this->plugin->afterGetOptionsMediaGalleryDataJson($blockMock, $resultJson); - $this->assertEquals(json_encode($expected), $result); - } -} diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php index 6b69888e6dd0924cf6e090bdb1eb0af2daddaab1..5a7886ea3ea640c2f276ffb7eedeaa243ff9e790 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/LinkManagementTest.php @@ -6,8 +6,8 @@ namespace Magento\ConfigurableProduct\Test\Unit\Model; +use Magento\ConfigurableProduct\Model\LinkManagement; use Magento\ConfigurableProduct\Model\Product\Type\Configurable; -use Magento\ConfigurableProduct\Test\Unit\Model\Product\ProductExtensionAttributes; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -146,15 +146,59 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $configurable = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) ->disableOriginalConstructor() + ->setMethods(['getId', 'getExtensionAttributes']) + ->getMock(); + $simple = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'getData']) ->getMock(); - $configurable->expects($this->any())->method('getId')->will($this->returnValue(666)); + $extensionAttributesMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductExtension::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getConfigurableProductOptions', 'setConfigurableProductOptions', 'setConfigurableProductLinks' + ]) + ->getMock(); + $optionMock = $this->getMockBuilder(\Magento\ConfigurableProduct\Api\Data\Option::class) + ->disableOriginalConstructor() + ->setMethods(['getProductAttribute', 'getAttributeId']) + ->getMock(); + $productAttributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) + ->disableOriginalConstructor() + ->setMethods(['getAttributeCode']) + ->getMock(); + $optionsFactoryMock = $this->getMockBuilder(\Magento\ConfigurableProduct\Helper\Product\Options\Factory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + $reflectionClass = new \ReflectionClass(\Magento\ConfigurableProduct\Model\LinkManagement::class); + $optionsFactoryReflectionProperty = $reflectionClass->getProperty('optionsFactory'); + $optionsFactoryReflectionProperty->setAccessible(true); + $optionsFactoryReflectionProperty->setValue($this->object, $optionsFactoryMock); - $simple = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) + $attributeFactoryMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory::class) ->disableOriginalConstructor() + ->setMethods(['create']) ->getMock(); + $attributeFactoryReflectionProperty = $reflectionClass->getProperty('attributeFactory'); + $attributeFactoryReflectionProperty->setAccessible(true); + $attributeFactoryReflectionProperty->setValue($this->object, $attributeFactoryMock); - $simple->expects($this->any())->method('getId')->will($this->returnValue(999)); + $attributeMock = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) + ->disableOriginalConstructor() + ->setMethods(['getCollection', 'getOptions', 'getId', 'getAttributeCode', 'getStoreLabel']) + ->getMock(); + $attributeOptionMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Option::class) + ->disableOriginalConstructor() + ->setMethods(['getValue', 'getLabel']) + ->getMock(); + + $attributeCollectionMock = $this->getMockBuilder( + \Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Attribute\Collection::class + ) + ->disableOriginalConstructor() + ->setMethods(['addFieldToFilter', 'getItems']) + ->getMock(); $this->productRepository->expects($this->at(0))->method('get')->with($productSku)->willReturn($configurable); $this->productRepository->expects($this->at(1))->method('get')->with($childSku)->willReturn($simple); @@ -164,15 +208,30 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $this->returnValue([0 => [1, 2, 3]]) ); - $extensionAttributes = $this->getMockBuilder(ProductExtensionAttributes::class) - ->setMethods(['setConfigurableProductLinks']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); + $configurable->expects($this->any())->method('getId')->will($this->returnValue(666)); + $simple->expects($this->any())->method('getId')->will($this->returnValue(999)); + + $configurable->expects($this->any())->method('getExtensionAttributes')->willReturn($extensionAttributesMock); + $extensionAttributesMock->expects($this->any()) + ->method('getConfigurableProductOptions') + ->willReturn([$optionMock]); + $optionMock->expects($this->any())->method('getProductAttribute')->willReturn($productAttributeMock); + $productAttributeMock->expects($this->any())->method('getAttributeCode')->willReturn('color'); + $simple->expects($this->any())->method('getData')->willReturn('color'); + $optionMock->expects($this->any())->method('getAttributeId')->willReturn('1'); - $configurable->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttributes); - $extensionAttributes->expects($this->once())->method('setConfigurableProductLinks')->willReturnSelf(); + $optionsFactoryMock->expects($this->any())->method('create')->willReturn([$optionMock]); + $attributeFactoryMock->expects($this->any())->method('create')->willReturn($attributeMock); + $attributeMock->expects($this->any())->method('getCollection')->willReturn($attributeCollectionMock); + $attributeCollectionMock->expects($this->any())->method('addFieldToFilter')->willReturnSelf(); + $attributeCollectionMock->expects($this->any())->method('getItems')->willReturn([$attributeMock]); - $configurable->expects($this->once())->method('save'); + $attributeMock->expects($this->any())->method('getOptions')->willReturn([$attributeOptionMock]); + + $extensionAttributesMock->expects($this->any())->method('setConfigurableProductOptions'); + $extensionAttributesMock->expects($this->any())->method('setConfigurableProductLinks'); + + $this->productRepository->expects($this->once())->method('save'); $this->assertTrue(true, $this->object->addChild($productSku, $childSku)); } @@ -243,15 +302,13 @@ class LinkManagementTest extends \PHPUnit_Framework_TestCase $productType->expects($this->once())->method('getUsedProducts') ->will($this->returnValue([$option])); - $extensionAttributes = $this->getMockBuilder(ProductExtensionAttributes::class) + $extensionAttributesMock = $this->getMockBuilder(\Magento\Framework\Api\ExtensionAttributesInterface::class) ->setMethods(['setConfigurableProductLinks']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $product->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttributes); - $extensionAttributes->expects($this->once())->method('setConfigurableProductLinks')->willReturnSelf(); - - $product->expects($this->once())->method('save'); + $product->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttributesMock); + $this->productRepository->expects($this->once())->method('save'); $this->assertTrue($this->object->removeChild($productSku, $childSku)); } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php index 0e2cfc0630226d500db55db0fd0fb0b60bb4d121..8db61bb5e0a4322ff3d614348616dfc0e2f34e27 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Price/ConfigurablePriceResolverTest.php @@ -5,13 +5,15 @@ */ namespace Magento\ConfigurableProduct\Test\Unit\Pricing\Price; -use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface; +use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase { - /** @var ConfigurableOptionsProviderInterface */ - private $cofigurableOptionProvider; + /** + * @var LowestPriceOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $lowestPriceOptionsProvider; /** * @var \Magento\ConfigurableProduct\Pricing\Price\ConfigurablePriceResolver @@ -19,12 +21,12 @@ class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase protected $resolver; /** - * @var PHPUnit_Framework_MockObject_MockObject | \Magento\ConfigurableProduct\Model\Product\Type\Configurable + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\ConfigurableProduct\Model\Product\Type\Configurable */ protected $configurable; /** - * @var PHPUnit_Framework_MockObject_MockObject | \Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface + * @var \PHPUnit_Framework_MockObject_MockObject | \Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface */ protected $priceResolver; @@ -36,8 +38,7 @@ class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase $className = \Magento\ConfigurableProduct\Pricing\Price\PriceResolverInterface::class; $this->priceResolver = $this->getMockForAbstractClass($className, [], '', false, true, true, ['resolvePrice']); - $this->cofigurableOptionProvider = $this->getMockBuilder(ConfigurableOptionsProviderInterface::class) - ->disableOriginalConstructor()->getMock(); + $this->lowestPriceOptionsProvider = $this->getMock(LowestPriceOptionsProviderInterface::class); $objectManager = new ObjectManager($this); $this->resolver = $objectManager->getObject( @@ -45,7 +46,7 @@ class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase [ 'priceResolver' => $this->priceResolver, 'configurable' => $this->configurable, - 'configurableOptionsProvider' => $this->cofigurableOptionProvider, + 'lowestPriceOptionsProvider' => $this->lowestPriceOptionsProvider, ] ); } @@ -63,7 +64,7 @@ class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase $product->expects($this->once())->method('getSku')->willReturn('Kiwi'); - $this->cofigurableOptionProvider->expects($this->once())->method('getProducts')->willReturn([]); + $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn([]); $this->resolver->resolvePrice($product); } @@ -83,8 +84,11 @@ class ConfigurablePriceResolverTest extends \PHPUnit_Framework_TestCase $product->expects($this->never())->method('getSku'); - $this->cofigurableOptionProvider->expects($this->once())->method('getProducts')->willReturn([$product]); - $this->priceResolver->expects($this->atLeastOnce())->method('resolvePrice')->willReturn($price); + $this->lowestPriceOptionsProvider->expects($this->once())->method('getProducts')->willReturn([$product]); + $this->priceResolver->expects($this->once()) + ->method('resolvePrice') + ->with($product) + ->willReturn($price); $this->assertEquals($expectedValue, $this->resolver->resolvePrice($product)); } diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php index 4dbcfed5315252c62494b6ede30d4d14f9a598b2..b102e1d81f48ea421ce29a7cd914a758e94ccb68 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php +++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Pricing/Render/FinalPriceBoxTest.php @@ -7,8 +7,9 @@ namespace Magento\ConfigurableProduct\Test\Unit\Pricing\Render; use Magento\Catalog\Pricing\Price\FinalPrice; use Magento\Catalog\Pricing\Price\RegularPrice; -use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface; +use Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface; use Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase { @@ -33,9 +34,9 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase private $rendererPool; /** - * @var ConfigurableOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject + * @var LowestPriceOptionsProviderInterface|\PHPUnit_Framework_MockObject_MockObject */ - private $configurableOptionsProvider; + private $lowestPriceOptionsProvider; /** * @var FinalPriceBox @@ -59,15 +60,18 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); - $this->configurableOptionsProvider = $this->getMockBuilder(ConfigurableOptionsProviderInterface::class) + $this->lowestPriceOptionsProvider = $this->getMockBuilder(LowestPriceOptionsProviderInterface::class) ->getMockForAbstractClass(); - $this->model = new FinalPriceBox( - $this->context, - $this->saleableItem, - $this->price, - $this->rendererPool, - $this->configurableOptionsProvider + $this->model = (new ObjectManager($this))->getObject( + FinalPriceBox::class, + [ + 'context' => $this->context, + 'saleableItem' => $this->saleableItem, + 'price' => $this->price, + 'rendererPool' => $this->rendererPool, + 'lowestPriceOptionsProvider' => $this->lowestPriceOptionsProvider, + ] ); } @@ -115,7 +119,7 @@ class FinalPriceBoxTest extends \PHPUnit_Framework_TestCase ->method('getPriceInfo') ->willReturn($priceInfoMock); - $this->configurableOptionsProvider->expects($this->once()) + $this->lowestPriceOptionsProvider->expects($this->once()) ->method('getProducts') ->with($this->saleableItem) ->willReturn([$productMock]); diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json index 8dda841250ff126d0fed90a03dd4272169266392..755b94a600a099a0ada188d435f3682cf6736e69 100644 --- a/app/code/Magento/ConfigurableProduct/composer.json +++ b/app/code/Magento/ConfigurableProduct/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-configurable-product", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-catalog-inventory": "100.2.*", diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml index 89b271de838f992e9acd93ecdd27bc321f59d54e..ca06ce5cc974bbab05232bdee33920ba87918ade 100644 --- a/app/code/Magento/ConfigurableProduct/etc/di.xml +++ b/app/code/Magento/ConfigurableProduct/etc/di.xml @@ -13,6 +13,7 @@ <preference for="Magento\ConfigurableProduct\Api\Data\OptionValueInterface" type="Magento\ConfigurableProduct\Model\Product\Type\Configurable\OptionValue" /> <preference for="Magento\ConfigurableProduct\Api\Data\ConfigurableItemOptionValueInterface" type="Magento\ConfigurableProduct\Model\Quote\Item\ConfigurableItemOptionValue" /> <preference for="Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface" type="Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProvider" /> + <preference for="Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProviderInterface" type="Magento\ConfigurableProduct\Pricing\Price\LowestPriceOptionsProvider" /> <type name="Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\Option"> <plugin name="configurable_product" type="Magento\ConfigurableProduct\Model\Quote\Item\QuantityValidator\Initializer\Option\Plugin\ConfigurableProduct" sortOrder="50" /> @@ -142,9 +143,6 @@ <type name="Magento\Catalog\Model\Product\Attribute\Backend\Price"> <plugin name="configurable" type="Magento\ConfigurableProduct\Model\Plugin\PriceBackend" sortOrder="100" /> </type> - <type name="\Magento\ProductVideo\Block\Product\View\Gallery"> - <plugin name="product_video_gallery" type="\Magento\ConfigurableProduct\Block\Plugin\Product\Media\Gallery" /> - </type> <type name="Magento\ConfigurableProduct\Model\Product\Type\Configurable"> <arguments> <argument name="cache" xsi:type="object">Magento\Framework\App\Cache\Type\Collection</argument> diff --git a/app/code/Magento/Contact/composer.json b/app/code/Magento/Contact/composer.json index b541f1e7a0ed3afe8f8bfb8c25b7f27282bc9372..377e46256c0886c5479cbe256451310bac14b896 100644 --- a/app/code/Magento/Contact/composer.json +++ b/app/code/Magento/Contact/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-contact", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Cookie/composer.json b/app/code/Magento/Cookie/composer.json index 5222975723385a48adf043f695c08e319f11d57b..96a050f2ad20dfa94958fd11f7a24cfb73010642 100644 --- a/app/code/Magento/Cookie/composer.json +++ b/app/code/Magento/Cookie/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-cookie", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/app/code/Magento/Cron/composer.json b/app/code/Magento/Cron/composer.json index b1155c028f91971b5508269a06ebef675032e5b5..75a7ce27a2aa110d55398a0588ae4f4907c724c2 100644 --- a/app/code/Magento/Cron/composer.json +++ b/app/code/Magento/Cron/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-cron", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/app/code/Magento/CurrencySymbol/composer.json b/app/code/Magento/CurrencySymbol/composer.json index 2cdfedadf7a38c7c792a27de4498772cc59c692b..29f0bd7435eef99279f54273163f17041d64a351 100644 --- a/app/code/Magento/CurrencySymbol/composer.json +++ b/app/code/Magento/CurrencySymbol/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-currency-symbol", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-page-cache": "100.2.*", diff --git a/app/code/Magento/Customer/Block/Account/AuthorizationLink.php b/app/code/Magento/Customer/Block/Account/AuthorizationLink.php index f2e2f1d0bd77f24408dc3f9f7492efce5775fe18..9cb3f8fdd94d613747595b3f03e3a492e6c0d342 100644 --- a/app/code/Magento/Customer/Block/Account/AuthorizationLink.php +++ b/app/code/Magento/Customer/Block/Account/AuthorizationLink.php @@ -6,13 +6,14 @@ namespace Magento\Customer\Block\Account; use Magento\Customer\Model\Context; +use Magento\Customer\Block\Account\SortLinkInterface; /** * Customer authorization link * * @SuppressWarnings(PHPMD.DepthOfInheritance) */ -class AuthorizationLink extends \Magento\Framework\View\Element\Html\Link +class AuthorizationLink extends \Magento\Framework\View\Element\Html\Link implements SortLinkInterface { /** * Customer session @@ -88,4 +89,12 @@ class AuthorizationLink extends \Magento\Framework\View\Element\Html\Link { return $this->httpContext->getValue(Context::CONTEXT_AUTH); } + + /** + * {@inheritdoc} + */ + public function getSortOrder() + { + return $this->getData(self::SORT_ORDER); + } } diff --git a/app/code/Magento/Customer/Block/Account/Delimiter.php b/app/code/Magento/Customer/Block/Account/Delimiter.php new file mode 100644 index 0000000000000000000000000000000000000000..31ded827184eb810cbffba562d2e1b388e573c5f --- /dev/null +++ b/app/code/Magento/Customer/Block/Account/Delimiter.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Block\Account; + +/** + * Class for delimiter. + */ +class Delimiter extends \Magento\Framework\View\Element\Template implements SortLinkInterface +{ + /** + * {@inheritdoc} + */ + public function getSortOrder() + { + return $this->getData(self::SORT_ORDER); + } +} diff --git a/app/code/Magento/Customer/Block/Account/Link.php b/app/code/Magento/Customer/Block/Account/Link.php index 59961971a0ca8c697980f621e7b3777bd7569823..d37a9a548d44ffd5ef97ea0da05ee17a7149b38b 100644 --- a/app/code/Magento/Customer/Block/Account/Link.php +++ b/app/code/Magento/Customer/Block/Account/Link.php @@ -5,12 +5,14 @@ */ namespace Magento\Customer\Block\Account; +use Magento\Customer\Block\Account\SortLinkInterface; + /** * Class Link * * @SuppressWarnings(PHPMD.DepthOfInheritance) */ -class Link extends \Magento\Framework\View\Element\Html\Link +class Link extends \Magento\Framework\View\Element\Html\Link implements SortLinkInterface { /** * @var \Magento\Customer\Model\Url @@ -38,4 +40,12 @@ class Link extends \Magento\Framework\View\Element\Html\Link { return $this->_customerUrl->getAccountUrl(); } + + /** + * {@inheritdoc} + */ + public function getSortOrder() + { + return $this->getData(self::SORT_ORDER); + } } diff --git a/app/code/Magento/Customer/Block/Account/Navigation.php b/app/code/Magento/Customer/Block/Account/Navigation.php new file mode 100644 index 0000000000000000000000000000000000000000..d8644f5565f3b6a8a4318c914719bc6c9fec3867 --- /dev/null +++ b/app/code/Magento/Customer/Block/Account/Navigation.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Block\Account; + +use \Magento\Framework\View\Element\Html\Links; +use \Magento\Customer\Block\Account\SortLinkInterface; + +/** + * Class for sorting links in navigation panels. + */ +class Navigation extends Links +{ + /** + * {@inheritdoc} + */ + public function getLinks() + { + $links = $this->_layout->getChildBlocks($this->getNameInLayout()); + $sortableLink = []; + foreach ($links as $key => $link) { + if ($link instanceof SortLinkInterface) { + $sortableLink[] = $link; + unset($links[$key]); + } + } + + usort($sortableLink, [$this, "compare"]); + return array_merge($sortableLink, $links); + } + + /** + * Compare sortOrder in links. + * + * @param SortLinkInterface $firstLink + * @param SortLinkInterface $secondLink + * @return int + * @SuppressWarnings(PHPMD.UnusedPrivateMethod) + */ + private function compare(SortLinkInterface $firstLink, SortLinkInterface $secondLink) + { + return ($firstLink->getSortOrder() < $secondLink->getSortOrder()); + } +} diff --git a/app/code/Magento/Customer/Block/Account/SortLink.php b/app/code/Magento/Customer/Block/Account/SortLink.php new file mode 100644 index 0000000000000000000000000000000000000000..2c60e804b7b216ebd26f8baefb8a3addd3fd9176 --- /dev/null +++ b/app/code/Magento/Customer/Block/Account/SortLink.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Block\Account; + +/** + * Class for sortable links. + */ +class SortLink extends \Magento\Framework\View\Element\Html\Link\Current implements SortLinkInterface +{ + /** + * {@inheritdoc} + */ + public function getSortOrder() + { + return $this->getData(self::SORT_ORDER); + } +} diff --git a/app/code/Magento/Customer/Block/Account/SortLinkInterface.php b/app/code/Magento/Customer/Block/Account/SortLinkInterface.php new file mode 100644 index 0000000000000000000000000000000000000000..815f4e5adf2fdc29993ea0bf9743c5049eb84967 --- /dev/null +++ b/app/code/Magento/Customer/Block/Account/SortLinkInterface.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Block\Account; + +/** + * Interface for sortable links. + * @api + */ +interface SortLinkInterface +{ + /**#@+ + * Constant for confirmation status + */ + const SORT_ORDER = 'sortOrder'; + /**#@-*/ + + /** + * Get sort order for block. + * + * @return int + */ + public function getSortOrder(); +} diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php index 77bf7af2a4031ac71488b7644225a6c13dc520f2..fd2ceaf09695bfb4a413bc6f680f254ca4edca4c 100644 --- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php +++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php @@ -72,6 +72,7 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index ) { $metadataForm = $this->getMetadataForm($entityType, $formCode, $scope); $formData = $metadataForm->extractData($this->getRequest(), $scope); + $formData = $metadataForm->compactData($formData); // Initialize additional attributes /** @var \Magento\Framework\DataObject $object */ @@ -81,11 +82,6 @@ class Save extends \Magento\Customer\Controller\Adminhtml\Index $formData[$attributeCode] = isset($requestData[$attributeCode]) ? $requestData[$attributeCode] : false; } - $result = $metadataForm->compactData($formData); - - // Re-initialize additional attributes - $formData = array_replace($formData, $result); - // Unset unused attributes $formAttributes = $metadataForm->getAttributes(); foreach ($formAttributes as $attribute) { diff --git a/app/code/Magento/Customer/Controller/Section/Load.php b/app/code/Magento/Customer/Controller/Section/Load.php index 19b361b2c830d763681184b5055f0d477c4641ae..c1ec593e4a7aeec51a53323052aeae62cb7036ff 100644 --- a/app/code/Magento/Customer/Controller/Section/Load.php +++ b/app/code/Magento/Customer/Controller/Section/Load.php @@ -5,10 +5,10 @@ */ namespace Magento\Customer\Controller\Section; +use Magento\Customer\CustomerData\Section\Identifier; use Magento\Customer\CustomerData\SectionPoolInterface; use Magento\Framework\App\Action\Context; use Magento\Framework\Controller\Result\JsonFactory; -use Magento\Framework\Exception\LocalizedException; /** * Customer section controller @@ -22,6 +22,7 @@ class Load extends \Magento\Framework\App\Action\Action /** * @var Identifier + * @deprecated */ protected $sectionIdentifier; @@ -30,26 +31,34 @@ class Load extends \Magento\Framework\App\Action\Action */ protected $sectionPool; + /** + * @var \Magento\Framework\Escaper + */ + private $escaper; + /** * @param Context $context * @param JsonFactory $resultJsonFactory - * @param \Magento\Customer\CustomerData\Section\Identifier $sectionIdentifier + * @param Identifier $sectionIdentifier * @param SectionPoolInterface $sectionPool + * @param Escaper $escaper */ public function __construct( Context $context, JsonFactory $resultJsonFactory, - \Magento\Customer\CustomerData\Section\Identifier $sectionIdentifier, - SectionPoolInterface $sectionPool + Identifier $sectionIdentifier, + SectionPoolInterface $sectionPool, + \Magento\Framework\Escaper $escaper = null ) { parent::__construct($context); $this->resultJsonFactory = $resultJsonFactory; $this->sectionIdentifier = $sectionIdentifier; $this->sectionPool = $sectionPool; + $this->escaper = $escaper ?: $this->_objectManager->get(\Magento\Framework\Escaper::class); } /** - * @return \Magento\Framework\Controller\Result\Json|\Magento\Framework\Controller\Result\Redirect + * @return \Magento\Framework\Controller\Result\Json */ public function execute() { @@ -60,7 +69,7 @@ class Load extends \Magento\Framework\App\Action\Action $sectionNames = $sectionNames ? array_unique(\explode(',', $sectionNames)) : null; $updateSectionId = $this->getRequest()->getParam('update_section_id'); - if ('false' == $updateSectionId) { + if ('false' === $updateSectionId) { $updateSectionId = false; } $response = $this->sectionPool->getSectionsData($sectionNames, (bool)$updateSectionId); @@ -70,7 +79,7 @@ class Load extends \Magento\Framework\App\Action\Action \Zend\Http\AbstractMessage::VERSION_11, 'Bad Request' ); - $response = ['message' => $e->getMessage()]; + $response = ['message' => $this->escaper->escapeHtml($e->getMessage())]; } return $resultJson->setData($response); diff --git a/app/code/Magento/Customer/Model/ResourceModel/Group.php b/app/code/Magento/Customer/Model/ResourceModel/Group.php index 7496940fe335067df022976b201e0d4ab85d9211..5fb02997cd73117eda354f2e1bf2617d41b10d77 100644 --- a/app/code/Magento/Customer/Model/ResourceModel/Group.php +++ b/app/code/Magento/Customer/Model/ResourceModel/Group.php @@ -129,4 +129,65 @@ class Group extends \Magento\Framework\Model\ResourceModel\Db\VersionControl\Abs $group->setCode(substr($group->getCode(), 0, $group::GROUP_CODE_MAX_LENGTH)); return parent::_beforeSave($group); } + + /** + * {@inheritdoc} + */ + protected function _afterSave(\Magento\Framework\Model\AbstractModel $object) + { + if ($object->getId() == \Magento\Customer\Model\Group::CUST_GROUP_ALL) { + $this->skipReservedId($object); + } + + return $this; + } + + /** + * Here we do not allow to save systems reserved ID. + * + * @param \Magento\Framework\Model\AbstractModel $object + * @throws \Magento\Framework\Exception\LocalizedException + * @return void + */ + private function skipReservedId(\Magento\Framework\Model\AbstractModel $object) + { + $tableFieldsWithoutIdField = $this->getTableFieldsWithoutIdField(); + $select = $this->getConnection()->select(); + $select->from( + [$this->getMainTable()], + $tableFieldsWithoutIdField + ) + ->where('customer_group_id = ?', \Magento\Customer\Model\Group::CUST_GROUP_ALL); + + $query = $this->getConnection()->insertFromSelect( + $select, + $this->getMainTable(), + $tableFieldsWithoutIdField + ); + $this->getConnection()->query($query); + $lastInsertId = $this->getConnection()->lastInsertId(); + + $query = $this->getConnection()->deleteFromSelect( + $select, + $this->getMainTable() + ); + $this->getConnection()->query($query); + + $object->setId($lastInsertId); + } + + /** + * Get main table fields except of ID field. + * + * @return array + */ + private function getTableFieldsWithoutIdField() + { + $fields = $this->getConnection()->describeTable($this->getMainTable()); + if (isset($fields['customer_group_id'])) { + unset($fields['customer_group_id']); + } + + return array_keys($fields); + } } diff --git a/app/code/Magento/Customer/Setup/UpgradeSchema.php b/app/code/Magento/Customer/Setup/UpgradeSchema.php old mode 100644 new mode 100755 index 18fc9b9f8d597bc3789d0053192497292be807b9..33ec2352e865d0b6fd5cbbde5d9646b5dd92975d --- a/app/code/Magento/Customer/Setup/UpgradeSchema.php +++ b/app/code/Magento/Customer/Setup/UpgradeSchema.php @@ -108,6 +108,96 @@ class UpgradeSchema implements UpgradeSchemaInterface ); } + if (version_compare($context->getVersion(), '2.0.10', '<')) { + $foreignKeys = $this->getForeignKeys($setup); + $this->dropForeignKeys($setup, $foreignKeys); + $this->alterTables($setup, $foreignKeys); + $this->createForeignKeys($setup, $foreignKeys); + } + $setup->endSetup(); } + + /** + * @param SchemaSetupInterface $setup + * @param array $keys + * @return void + */ + private function alterTables(SchemaSetupInterface $setup, array $keys) + { + $setup->getConnection()->modifyColumn( + $setup->getTable('customer_group'), + 'customer_group_id', + [ + 'type' => 'integer', + 'unsigned' => true, + 'identity' => true, + 'nullable' => false + ] + ); + foreach ($keys as $key) { + $setup->getConnection()->modifyColumn( + $key['TABLE_NAME'], + $key['COLUMN_NAME'], + [ + 'type' => 'integer', + 'unsigned' => true, + 'nullable' => false + ] + ); + } + } + + /** + * @param SchemaSetupInterface $setup + * @param array $keys + * @return void + */ + private function dropForeignKeys(SchemaSetupInterface $setup, array $keys) + { + foreach ($keys as $key) { + $setup->getConnection()->dropForeignKey($key['TABLE_NAME'], $key['FK_NAME']); + } + } + + /** + * @param SchemaSetupInterface $setup + * @param array $keys + * @return void + */ + private function createForeignKeys(SchemaSetupInterface $setup, array $keys) + { + foreach ($keys as $key) { + $setup->getConnection()->addForeignKey( + $key['FK_NAME'], + $key['TABLE_NAME'], + $key['COLUMN_NAME'], + $key['REF_TABLE_NAME'], + $key['REF_COLUMN_NAME'], + $key['ON_DELETE'] + ); + } + } + + /** + * @param SchemaSetupInterface $setup + * @return array + */ + private function getForeignKeys(SchemaSetupInterface $setup) + { + $foreignKeys = []; + $keysTree = $setup->getConnection()->getForeignKeysTree(); + foreach ($keysTree as $indexes) { + foreach ($indexes as $index) { + if ( + $index['REF_TABLE_NAME'] == $setup->getTable('customer_group') + && $index['REF_COLUMN_NAME'] == 'customer_group_id' + ) { + $foreignKeys[] = $index; + } + + } + } + return $foreignKeys; + } } diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php index 8fb48fe9f87e76dcf4f5d489a830bb15bf910c79..11fd1b5a7fc3356f54cc1e72b76e111b22446383 100644 --- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php +++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php @@ -304,33 +304,28 @@ class SaveTest extends \PHPUnit_Framework_TestCase ], 'subscription' => $subscription, ]; - $filteredData = [ + $extractedData = [ 'entity_id' => $customerId, 'code' => 'value', 'coolness' => false, 'disable_auto_group_change' => 'false', ]; - $dataToCompact = [ + $compactedData = [ 'entity_id' => $customerId, 'code' => 'value', 'coolness' => false, 'disable_auto_group_change' => 'false', - CustomerInterface::DEFAULT_BILLING => false, - CustomerInterface::DEFAULT_SHIPPING => false, - 'confirmation' => false, - 'sendemail_store_id' => false, - 'extension_attributes' => false, + CustomerInterface::DEFAULT_BILLING => 2, + CustomerInterface::DEFAULT_SHIPPING => 2 ]; - $addressFilteredData = [ + $addressExtractedData = [ 'entity_id' => $addressId, - 'default_billing' => 'true', - 'default_shipping' => 'true', 'code' => 'value', 'coolness' => false, 'region' => 'region', 'region_id' => 'region_id', ]; - $addressDataToCompact = [ + $addressCompactedData = [ 'entity_id' => $addressId, 'default_billing' => 'true', 'default_shipping' => 'true', @@ -430,11 +425,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase $customerFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'customer') - ->willReturn($filteredData); + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('compactData') - ->with($dataToCompact) - ->willReturn($filteredData); + ->with($extractedData) + ->willReturn($compactedData); $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); @@ -445,11 +440,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase $customerAddressFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'address/' . $addressId) - ->willReturn($addressFilteredData); + ->willReturn($addressExtractedData); $customerAddressFormMock->expects($this->once()) ->method('compactData') - ->with($addressDataToCompact) - ->willReturn($addressFilteredData); + ->with($addressExtractedData) + ->willReturn($addressCompactedData); $customerAddressFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); @@ -625,8 +620,6 @@ class SaveTest extends \PHPUnit_Framework_TestCase '_template_' => '_template_', $addressId => [ 'entity_id' => $addressId, - 'default_billing' => 'false', - 'default_shipping' => 'false', 'code' => 'value', 'coolness' => false, 'region' => 'region', @@ -635,32 +628,12 @@ class SaveTest extends \PHPUnit_Framework_TestCase ], 'subscription' => $subscription, ]; - $filteredData = [ + $extractedData = [ 'coolness' => false, 'disable_auto_group_change' => 'false', ]; - $dataToCompact = [ - 'coolness' => false, - 'disable_auto_group_change' => 'false', - CustomerInterface::DEFAULT_BILLING => false, - CustomerInterface::DEFAULT_SHIPPING => false, - 'confirmation' => false, - 'sendemail_store_id' => false, - 'extension_attributes' => false, - ]; - $addressFilteredData = [ + $addressExtractedData = [ 'entity_id' => $addressId, - 'default_billing' => 'false', - 'default_shipping' => 'false', - 'code' => 'value', - 'coolness' => false, - 'region' => 'region', - 'region_id' => 'region_id', - ]; - $addressDataToCompact = [ - 'entity_id' => $addressId, - 'default_billing' => 'false', - 'default_shipping' => 'false', 'code' => 'value', 'coolness' => false, 'region' => 'region', @@ -739,11 +712,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase $customerFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'customer') - ->willReturn($filteredData); + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('compactData') - ->with($dataToCompact) - ->willReturn($filteredData); + ->with($extractedData) + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); @@ -754,11 +727,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase $customerAddressFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'address/' . $addressId) - ->willReturn($addressFilteredData); + ->willReturn($addressExtractedData); $customerAddressFormMock->expects($this->once()) ->method('compactData') - ->with($addressDataToCompact) - ->willReturn($addressFilteredData); + ->with($addressExtractedData) + ->willReturn($addressExtractedData); $customerAddressFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); @@ -910,19 +883,10 @@ class SaveTest extends \PHPUnit_Framework_TestCase ], 'subscription' => $subscription, ]; - $filteredData = [ + $extractedData = [ 'coolness' => false, 'disable_auto_group_change' => 'false', ]; - $dataToCompact = [ - 'coolness' => false, - 'disable_auto_group_change' => 'false', - CustomerInterface::DEFAULT_BILLING => false, - CustomerInterface::DEFAULT_SHIPPING => false, - 'confirmation' => false, - 'sendemail_store_id' => false, - 'extension_attributes' => false, - ]; /** @var AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject $customerFormMock */ $attributeMock = $this->getMockBuilder( @@ -971,11 +935,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase $customerFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'customer') - ->willReturn($filteredData); + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('compactData') - ->with($dataToCompact) - ->willReturn($filteredData); + ->with($extractedData) + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); @@ -1062,19 +1026,10 @@ class SaveTest extends \PHPUnit_Framework_TestCase ], 'subscription' => $subscription, ]; - $filteredData = [ + $extractedData = [ 'coolness' => false, 'disable_auto_group_change' => 'false', ]; - $dataToCompact = [ - 'coolness' => false, - 'disable_auto_group_change' => 'false', - CustomerInterface::DEFAULT_BILLING => false, - CustomerInterface::DEFAULT_SHIPPING => false, - 'confirmation' => false, - 'sendemail_store_id' => false, - 'extension_attributes' => false, - ]; /** @var AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject $customerFormMock */ $attributeMock = $this->getMockBuilder( @@ -1124,11 +1079,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase $customerFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'customer') - ->willReturn($filteredData); + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('compactData') - ->with($dataToCompact) - ->willReturn($filteredData); + ->with($extractedData) + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); @@ -1214,18 +1169,9 @@ class SaveTest extends \PHPUnit_Framework_TestCase ], 'subscription' => $subscription, ]; - $filteredData = [ - 'coolness' => false, - 'disable_auto_group_change' => 'false', - ]; - $dataToCompact = [ + $extractedData = [ 'coolness' => false, 'disable_auto_group_change' => 'false', - CustomerInterface::DEFAULT_BILLING => false, - CustomerInterface::DEFAULT_SHIPPING => false, - 'confirmation' => false, - 'sendemail_store_id' => false, - 'extension_attributes' => false, ]; /** @var AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject $customerFormMock */ @@ -1275,11 +1221,11 @@ class SaveTest extends \PHPUnit_Framework_TestCase $customerFormMock->expects($this->once()) ->method('extractData') ->with($this->requestMock, 'customer') - ->willReturn($filteredData); + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('compactData') - ->with($dataToCompact) - ->willReturn($filteredData); + ->with($extractedData) + ->willReturn($extractedData); $customerFormMock->expects($this->once()) ->method('getAttributes') ->willReturn($attributes); diff --git a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php index 9eed3d2b044ca166d35703d35e1fd1995e181108..28697fa5cf85faf8bb33dd61c9f5816fc0b60eed 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/AccountManagementTest.php @@ -1146,7 +1146,7 @@ class AccountManagementTest extends \PHPUnit_Framework_TestCase $storeId = 1; - mt_srand(mt_rand() + (100000000 * microtime()) % PHP_INT_MAX); + 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()) @@ -1168,7 +1168,7 @@ class AccountManagementTest extends \PHPUnit_Framework_TestCase $templateIdentifier = 'Template Identifier'; $sender = 'Sender'; - mt_srand(mt_rand() + (100000000 * microtime()) % PHP_INT_MAX); + 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()) @@ -1194,7 +1194,7 @@ class AccountManagementTest extends \PHPUnit_Framework_TestCase $templateIdentifier = 'Template Identifier'; $sender = 'Sender'; - mt_srand(mt_rand() + (100000000 * microtime()) % PHP_INT_MAX); + 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); diff --git a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php index 6520556842a79d5ad4fb44f05c7706e6dfacba69..8305114ad70cd1edfb15f6615b39f12ee41d8ec3 100644 --- a/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php +++ b/app/code/Magento/Customer/Test/Unit/Model/ResourceModel/GroupTest.php @@ -9,6 +9,7 @@ namespace Magento\Customer\Test\Unit\Model\ResourceModel; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -36,6 +37,16 @@ class GroupTest extends \PHPUnit_Framework_TestCase /** @var \PHPUnit_Framework_MockObject_MockObject */ protected $relationProcessorMock; + /** + * @var Snapshot|\PHPUnit_Framework_MockObject_MockObject + */ + private $snapshotMock; + + /** + * Setting up dependencies. + * + * @return void + */ protected function setUp() { $this->resource = $this->getMock(\Magento\Framework\App\ResourceConnection::class, [], [], '', false); @@ -67,10 +78,18 @@ class GroupTest extends \PHPUnit_Framework_TestCase false ); + $this->snapshotMock = $this->getMock( + \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot::class, + [], + [], + '', + false + ); + $transactionManagerMock = $this->getMock( \Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface::class ); - $transactionManagerMock->expects($this->once()) + $transactionManagerMock->expects($this->any()) ->method('start') ->willReturn($this->getMock(\Magento\Framework\DB\Adapter\AdapterInterface::class)); $contextMock->expects($this->once()) @@ -86,10 +105,62 @@ class GroupTest extends \PHPUnit_Framework_TestCase 'context' => $contextMock, 'groupManagement' => $this->groupManagement, 'customersFactory' => $this->customersFactory, + 'entitySnapshot' => $this->snapshotMock ] ); } + /** + * Test for save() method when we try to save entity with system's reserved ID. + * + * @return void + */ + public function testSaveWithReservedId() + { + $expectedId = 55; + $this->snapshotMock->expects($this->once())->method('isModified')->willReturn(true); + $this->snapshotMock->expects($this->once())->method('registerSnapshot')->willReturnSelf(); + + $this->groupModel->expects($this->any())->method('getId') + ->willReturn(\Magento\Customer\Model\Group::CUST_GROUP_ALL); + $this->groupModel->expects($this->any())->method('getData') + ->willReturn([]); + $this->groupModel->expects($this->any())->method('isSaveAllowed') + ->willReturn(true); + $this->groupModel->expects($this->any())->method('getStoredData') + ->willReturn([]); + $this->groupModel->expects($this->once())->method('setId') + ->with($expectedId); + + $dbAdapter = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'lastInsertId', + 'describeTable', + 'update', + 'select' + ] + ) + ->getMockForAbstractClass(); + $dbAdapter->expects($this->any())->method('describeTable')->willReturn([]); + $dbAdapter->expects($this->any())->method('update')->willReturnSelf(); + $dbAdapter->expects($this->once())->method('lastInsertId')->willReturn($expectedId); + $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $dbAdapter->expects($this->any())->method('select')->willReturn($selectMock); + $selectMock->expects($this->any())->method('from')->willReturnSelf(); + $this->resource->expects($this->any())->method('getConnection')->willReturn($dbAdapter); + + $this->groupResourceModel->save($this->groupModel); + } + + /** + * Test for delete() method when we try to save entity with system's reserved ID. + * + * @return void + */ public function testDelete() { $dbAdapter = $this->getMock(\Magento\Framework\DB\Adapter\AdapterInterface::class); diff --git a/app/code/Magento/Customer/composer.json b/app/code/Magento/Customer/composer.json index 74f0ebc2b5e0026acbdc5061949c10ad04aa8d9c..53e1a270fb3b04a791a368a375ce7e8b1ef62d8d 100644 --- a/app/code/Magento/Customer/composer.json +++ b/app/code/Magento/Customer/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-customer", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-eav": "100.2.*", "magento/module-directory": "100.2.*", diff --git a/app/code/Magento/Customer/etc/di.xml b/app/code/Magento/Customer/etc/di.xml index b45d00ad0cdddcc1c46645569b92c7848feb8cff..9113895f2e58a2ead7088b54e32e693bf0c1e975 100644 --- a/app/code/Magento/Customer/etc/di.xml +++ b/app/code/Magento/Customer/etc/di.xml @@ -55,6 +55,8 @@ type="Magento\Customer\Model\Customer\Source\Group" /> <preference for="Magento\Customer\Model\Customer\Attribute\Source\GroupSourceLoggedInOnlyInterface" type="Magento\Customer\Model\Customer\Attribute\Source\Group"/> + <preference for="Magento\Customer\Block\Account\SortLinkInterface" + type="Magento\Customer\Block\Account\SortLink"/> <type name="Magento\Customer\Model\Session"> <arguments> <argument name="configShare" xsi:type="object">Magento\Customer\Model\Config\Share\Proxy</argument> diff --git a/app/code/Magento/Customer/etc/module.xml b/app/code/Magento/Customer/etc/module.xml index fd8307fc36657d8aacc419b2ad48006969cf68f0..f505adb98a0fab653636175a8e10c755521bd706 100644 --- a/app/code/Magento/Customer/etc/module.xml +++ b/app/code/Magento/Customer/etc/module.xml @@ -6,7 +6,7 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> - <module name="Magento_Customer" setup_version="2.0.9"> + <module name="Magento_Customer" setup_version="2.0.10"> <sequence> <module name="Magento_Eav"/> <module name="Magento_Directory"/> diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_account.xml b/app/code/Magento/Customer/view/frontend/layout/customer_account.xml index a96dfcd86e542d3ce3bce5fd9fcc39d434c28bcd..0f95cde6798fef89eb7e12673778b84c505029d3 100644 --- a/app/code/Magento/Customer/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Customer/view/frontend/layout/customer_account.xml @@ -9,24 +9,45 @@ <body> <attribute name="class" value="account"/> <referenceContainer name="sidebar.main"> - <block class="Magento\Framework\View\Element\Html\Links" name="customer_account_navigation" before="-" template="Magento_Customer::account/navigation.phtml"> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-account-link"> + <block class="Magento\Framework\View\Element\Template" template="Magento_Theme::html/collapsible.phtml" before="-"> + <arguments> + <argument name="block_css" xsi:type="string">account-nav</argument> + </arguments> + <block class="Magento\Customer\Block\Account\Navigation" name="customer_account_navigation" before="-"> <arguments> - <argument name="label" xsi:type="string" translate="true">Account Dashboard</argument> - <argument name="path" xsi:type="string">customer/account</argument> - </arguments> - </block> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-account-edit-link"> - <arguments> - <argument name="label" xsi:type="string" translate="true">Account Information</argument> - <argument name="path" xsi:type="string">customer/account/edit</argument> - </arguments> - </block> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-address-link"> - <arguments> - <argument name="label" xsi:type="string" translate="true">Address Book</argument> - <argument name="path" xsi:type="string">customer/address</argument> + <argument name="css_class" xsi:type="string">nav items</argument> </arguments> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-account-link"> + <arguments> + <argument name="label" xsi:type="string" translate="true">Account Dashboard</argument> + <argument name="path" xsi:type="string">customer/account</argument> + <argument name="sortOrder" xsi:type="number">250</argument> + </arguments> + </block> + <block class="Magento\Customer\Block\Account\Delimiter" name="customer-account-navigation-delimiter-1" template="Magento_Customer::account/navigation-delimiter.phtml"> + <arguments> + <argument name="sortOrder" xsi:type="number">200</argument> + </arguments> + </block> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-address-link"> + <arguments> + <argument name="label" xsi:type="string" translate="true">Address Book</argument> + <argument name="path" xsi:type="string">customer/address</argument> + <argument name="sortOrder" xsi:type="number">190</argument> + </arguments> + </block> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-account-edit-link"> + <arguments> + <argument name="label" xsi:type="string" translate="true">Account Information</argument> + <argument name="path" xsi:type="string">customer/account/edit</argument> + <argument name="sortOrder" xsi:type="number">180</argument> + </arguments> + </block> + <block class="Magento\Customer\Block\Account\Delimiter" name="customer-account-navigation-delimiter-2" template="Magento_Customer::account/navigation-delimiter.phtml"> + <arguments> + <argument name="sortOrder" xsi:type="number">130</argument> + </arguments> + </block> </block> </block> </referenceContainer> diff --git a/app/code/Magento/Customer/view/frontend/layout/default.xml b/app/code/Magento/Customer/view/frontend/layout/default.xml index 50580f73a9d3100dc4e17191f9d30804091e12b2..278c16b324da42c07b47c91d73d215e301af842f 100644 --- a/app/code/Magento/Customer/view/frontend/layout/default.xml +++ b/app/code/Magento/Customer/view/frontend/layout/default.xml @@ -11,6 +11,7 @@ <block class="Magento\Customer\Block\Account\Link" name="my-account-link"> <arguments> <argument name="label" xsi:type="string" translate="true">My Account</argument> + <argument name="sortOrder" xsi:type="number">110</argument> </arguments> </block> <block class="Magento\Customer\Block\Account\RegisterLink" name="register-link"> diff --git a/app/code/Magento/Customer/view/frontend/templates/account/navigation-delimiter.phtml b/app/code/Magento/Customer/view/frontend/templates/account/navigation-delimiter.phtml new file mode 100644 index 0000000000000000000000000000000000000000..bbc80e6686c4f4345286a185c6156a744b6a52f5 --- /dev/null +++ b/app/code/Magento/Customer/view/frontend/templates/account/navigation-delimiter.phtml @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +?> +<li class="nav item"> + <span class="delimiter"></span> +</li> diff --git a/app/code/Magento/CustomerImportExport/composer.json b/app/code/Magento/CustomerImportExport/composer.json index 533f40c7688e4aa8269bedfe84f5331a772b0b2a..cd02f6dbfb38b46bda32ebdcb87720124eab37e4 100644 --- a/app/code/Magento/CustomerImportExport/composer.json +++ b/app/code/Magento/CustomerImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-customer-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Deploy/composer.json b/app/code/Magento/Deploy/composer.json index 856e0d8b3e542a37cdd7fcf7554b9e4e158de245..ed0b8520ca645afa8af2506616f9dccde6965f08 100644 --- a/app/code/Magento/Deploy/composer.json +++ b/app/code/Magento/Deploy/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-deploy", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*", "magento/module-store": "100.2.*", "magento/module-require-js": "100.2.*", diff --git a/app/code/Magento/Developer/Model/View/Asset/PreProcessor/FrontendCompilation.php b/app/code/Magento/Developer/Model/View/Asset/PreProcessor/FrontendCompilation.php index 4573f15b016fa47bab5cc0fa7c7010644c4ada6e..8cd04f7d1e4c54804ba32c3c8b5235f9da3dd1db 100644 --- a/app/code/Magento/Developer/Model/View/Asset/PreProcessor/FrontendCompilation.php +++ b/app/code/Magento/Developer/Model/View/Asset/PreProcessor/FrontendCompilation.php @@ -74,10 +74,6 @@ class FrontendCompilation implements PreProcessorInterface */ public function process(PreProcessor\Chain $chain) { - $content = $chain->getContent(); - if (trim($content) !== '') { - return; - } try { $this->lockerProcess->lockProcess($this->lockName); @@ -88,7 +84,7 @@ class FrontendCompilation implements PreProcessorInterface /** @var FallbackContext $context */ $context = $chain->getAsset()->getContext(); - $result = $this->processContent($path, $content, $module, $context); + $result = $this->processContent($path, $chain->getContent(), $module, $context); $chain->setContent($result['content']); $chain->setContentType($result['sourceType']); } finally { @@ -107,14 +103,14 @@ class FrontendCompilation implements PreProcessorInterface */ private function processContent($path, $content, $module, FallbackContext $context) { - $sourceType = '#\.' . preg_quote(pathinfo($path, PATHINFO_EXTENSION), '#') . '$#'; + $sourceTypePattern = '#\.' . preg_quote(pathinfo($path, PATHINFO_EXTENSION), '#') . '$#'; foreach ($this->alternativeSource->getAlternativesExtensionsNames() as $name) { $asset = $this->assetBuilder->setArea($context->getAreaCode()) ->setTheme($context->getThemePath()) ->setLocale($context->getLocale()) ->setModule($module) - ->setPath(preg_replace($sourceType, '.' . $name, $path)) + ->setPath(preg_replace($sourceTypePattern, '.' . $name, $path)) ->build(); $processedContent = $this->assetSource->getContent($asset); @@ -129,7 +125,7 @@ class FrontendCompilation implements PreProcessorInterface return [ 'content' => $content, - 'sourceType' => $sourceType + 'sourceType' => pathinfo($path, PATHINFO_EXTENSION) ]; } } diff --git a/app/code/Magento/Developer/Test/Unit/Model/View/Asset/PreProcessor/FrontendCompilationTest.php b/app/code/Magento/Developer/Test/Unit/Model/View/Asset/PreProcessor/FrontendCompilationTest.php index 97f0b1d5a6bdb081a47f22884f6dac93b3a743f1..b43d91c5e4dc6bc7768c47b1f8599c73b5855932 100644 --- a/app/code/Magento/Developer/Test/Unit/Model/View/Asset/PreProcessor/FrontendCompilationTest.php +++ b/app/code/Magento/Developer/Test/Unit/Model/View/Asset/PreProcessor/FrontendCompilationTest.php @@ -189,38 +189,6 @@ class FrontendCompilationTest extends \PHPUnit_Framework_TestCase $frontendCompilation->process($this->getChainMockExpects('', 1, 1, $newContentType)); } - /** - * Run test for process method (content not empty) - */ - public function testProcessContentNotEmpty() - { - $chainMock = $this->getChainMock(); - $assetMock = $this->getAssetMock(); - - $chainMock->expects(self::once()) - ->method('getContent') - ->willReturn('test-content'); - - $chainMock->expects(self::never()) - ->method('getAsset') - ->willReturn($assetMock); - - $this->lockerProcessMock->expects(self::never()) - ->method('lockProcess'); - $this->lockerProcessMock->expects(self::never()) - ->method('unlockProcess'); - - $frontendCompilation = new FrontendCompilation( - $this->assetSourceMock, - $this->assetBuilderMock, - $this->alternativeSourceMock, - $this->lockerProcessMock, - 'lock' - ); - - $frontendCompilation->process($chainMock); - } - /** * @return Chain|\PHPUnit_Framework_MockObject_MockObject */ diff --git a/app/code/Magento/Developer/composer.json b/app/code/Magento/Developer/composer.json index 23f6d20c2aab74dc0f23c03f5727aa99379fbf54..73fd1c17750c6749bd0f89cd9fe55939fbe2d419 100644 --- a/app/code/Magento/Developer/composer.json +++ b/app/code/Magento/Developer/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-developer", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/framework": "100.2.*", "magento/module-config": "100.2.*" diff --git a/app/code/Magento/Dhl/composer.json b/app/code/Magento/Dhl/composer.json index e88cd535952d628628bbc984137430d5370a6ecd..15ca7882331a145ba1993ded371bd084bc8c83b3 100644 --- a/app/code/Magento/Dhl/composer.json +++ b/app/code/Magento/Dhl/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-dhl", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-shipping": "100.2.*", diff --git a/app/code/Magento/Directory/Model/Country/Postcode/Config/Reader.php b/app/code/Magento/Directory/Model/Country/Postcode/Config/Reader.php index 9ea0babbfd9982c3e9c5657e218668c384b47501..ed36ad3ace22b232c8071bbb195184066a2ebd50 100644 --- a/app/code/Magento/Directory/Model/Country/Postcode/Config/Reader.php +++ b/app/code/Magento/Directory/Model/Country/Postcode/Config/Reader.php @@ -12,7 +12,10 @@ class Reader extends \Magento\Framework\Config\Reader\Filesystem * * @var array */ - protected $_idAttributes = ['/config/zip' => 'countryCode']; + protected $_idAttributes = [ + '/config/zip' => 'countryCode', + '/config/zip/codes/code' => 'id', + ]; /** * Construct the FileSystem Reader Class diff --git a/app/code/Magento/Directory/composer.json b/app/code/Magento/Directory/composer.json index 9b3b253c26a35ed14c4de51c77624a10750ce5f2..82d8bca34da840e57511bb3912c9b531297d9e86 100644 --- a/app/code/Magento/Directory/composer.json +++ b/app/code/Magento/Directory/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-directory", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", diff --git a/app/code/Magento/Downloadable/Setup/UpgradeSchema.php b/app/code/Magento/Downloadable/Setup/UpgradeSchema.php new file mode 100755 index 0000000000000000000000000000000000000000..76ed3f2aae275196993757de9a5a91240773a902 --- /dev/null +++ b/app/code/Magento/Downloadable/Setup/UpgradeSchema.php @@ -0,0 +1,41 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Downloadable\Setup; + +use Magento\Framework\Setup\UpgradeSchemaInterface; +use Magento\Framework\Setup\ModuleContextInterface; +use Magento\Framework\Setup\SchemaSetupInterface; + +/** + * @codeCoverageIgnore + */ +class UpgradeSchema implements UpgradeSchemaInterface +{ + /** + * {@inheritdoc} + */ + public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context) + { + $setup->startSetup(); + + if (version_compare($context->getVersion(), '2.0.2', '<')) { + $tables = [ + 'catalog_product_index_price_downlod_idx', + 'catalog_product_index_price_downlod_tmp', + ]; + foreach ($tables as $table) { + $setup->getConnection()->modifyColumn( + $setup->getTable($table), + 'customer_group_id', + ['type' => 'integer', 'nullable' => false] + ); + } + } + + $setup->endSetup(); + } +} diff --git a/app/code/Magento/Downloadable/composer.json b/app/code/Magento/Downloadable/composer.json index 8d8814740ccd13248b1e7ab903052ca429567ead..ea8eef6436ef9174e9643c742684f29d1129799f 100644 --- a/app/code/Magento/Downloadable/composer.json +++ b/app/code/Magento/Downloadable/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-downloadable", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Downloadable/etc/module.xml b/app/code/Magento/Downloadable/etc/module.xml index a2fa1d9d569ddd6d4f7a42c9bf47bb449c3ac637..c6fd9c6a982e433d555b18ff5c35965550943e7e 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.1"> + <module name="Magento_Downloadable" setup_version="2.0.2"> <sequence> <module name="Magento_Catalog"/> </sequence> diff --git a/app/code/Magento/Downloadable/view/frontend/layout/customer_account.xml b/app/code/Magento/Downloadable/view/frontend/layout/customer_account.xml index 8d4f13f2bfb160c490ab88f100586d745877d17b..96bcb556edf0dd652b149244ee288f50abff618e 100644 --- a/app/code/Magento/Downloadable/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Downloadable/view/frontend/layout/customer_account.xml @@ -8,10 +8,11 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-downloadable-products-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-downloadable-products-link"> <arguments> <argument name="path" xsi:type="string">downloadable/customer/products</argument> <argument name="label" xsi:type="string">My Downloadable Products</argument> + <argument name="sortOrder" xsi:type="number">220</argument> </arguments> </block> </referenceBlock> diff --git a/app/code/Magento/DownloadableImportExport/composer.json b/app/code/Magento/DownloadableImportExport/composer.json index c99a0d6d05b822e792e24a27a69f68f240f8b002..4f95711b279d0f907934fc4cf2bc4b8056ff4817 100644 --- a/app/code/Magento/DownloadableImportExport/composer.json +++ b/app/code/Magento/DownloadableImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-downloadable-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-import-export": "100.2.*", "magento/module-catalog-import-export": "100.2.*", 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 9c2aeeec4abf5d2817df474c4a7c1cfbb818df6f..2b5bbc406d4690602acff51e79904a82aaad9151 100644 --- a/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php +++ b/app/code/Magento/Eav/Model/Entity/Attribute/Source/Table.php @@ -5,6 +5,9 @@ */ namespace Magento\Eav\Model\Entity\Attribute\Source; +use Magento\Framework\App\ObjectManager; +use Magento\Store\Model\StoreManagerInterface; + class Table extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource { /** @@ -24,6 +27,11 @@ class Table extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource */ protected $_attrOptionFactory; + /** + * @var StoreManagerInterface + */ + private $storeManager; + /** * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\CollectionFactory $attrOptionCollectionFactory * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\OptionFactory $attrOptionFactory @@ -47,24 +55,30 @@ class Table extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource public function getAllOptions($withEmpty = true, $defaultValues = false) { $storeId = $this->getAttribute()->getStoreId(); + if ($storeId === null) { + $storeId = $this->getStoreManager()->getStore()->getId(); + } if (!is_array($this->_options)) { $this->_options = []; } if (!is_array($this->_optionsDefault)) { $this->_optionsDefault = []; } - if (!isset($this->_options[$storeId])) { + $attributeId = $this->getAttribute()->getId(); + if (!isset($this->_options[$storeId][$attributeId])) { $collection = $this->_attrOptionCollectionFactory->create()->setPositionOrder( 'asc' )->setAttributeFilter( - $this->getAttribute()->getId() + $attributeId )->setStoreFilter( - $this->getAttribute()->getStoreId() + $storeId )->load(); - $this->_options[$storeId] = $collection->toOptionArray(); - $this->_optionsDefault[$storeId] = $collection->toOptionArray('default_value'); + $this->_options[$storeId][$attributeId] = $collection->toOptionArray(); + $this->_optionsDefault[$storeId][$attributeId] = $collection->toOptionArray('default_value'); } - $options = $defaultValues ? $this->_optionsDefault[$storeId] : $this->_options[$storeId]; + $options = $defaultValues + ? $this->_optionsDefault[$storeId][$attributeId] + : $this->_options[$storeId][$attributeId]; if ($withEmpty) { $options = $this->addEmptyOption($options); } @@ -72,6 +86,20 @@ class Table extends \Magento\Eav\Model\Entity\Attribute\Source\AbstractSource return $options; } + /** + * Get StoreManager dependency + * + * @return StoreManagerInterface + * @deprecated + */ + private function getStoreManager() + { + if ($this->storeManager === null) { + $this->storeManager = ObjectManager::getInstance()->get(StoreManagerInterface::class); + } + return $this->storeManager; + } + /** * Retrieve Option values array by ids * diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/TableTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/TableTest.php index fcae389b8e2e3d6d7e312055a305b4b615734c59..9b893bf9017681b584a611939f1332ee50fbc869 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/TableTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/TableTest.php @@ -6,6 +6,11 @@ namespace Magento\Eav\Test\Unit\Model\Entity\Attribute\Source; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource; +use Magento\Eav\Model\Entity\Attribute\AbstractAttribute; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection as AttributeOptionCollection; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -28,6 +33,31 @@ class TableTest extends \PHPUnit_Framework_TestCase */ private $attrOptionFactory; + /** + * @var AbstractSource | \PHPUnit_Framework_MockObject_MockObject + */ + private $sourceMock; + + /** + * @var AbstractAttribute | \PHPUnit_Framework_MockObject_MockObject + */ + private $abstractAttributeMock; + + /** + * @var StoreManagerInterface | \PHPUnit_Framework_MockObject_MockObject + */ + private $storeManagerMock; + + /** + * @var StoreInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $storeMock; + + /** + * @var AttributeOptionCollection|\PHPUnit_Framework_MockObject_MockObject + */ + private $attributeOptionCollectionMock; + protected function setUp() { $objectManager = new ObjectManager($this); @@ -48,6 +78,11 @@ class TableTest extends \PHPUnit_Framework_TestCase false ); + $this->attributeOptionCollectionMock = $this->getMockBuilder(AttributeOptionCollection::class) + ->setMethods(['toOptionArray']) + ->disableOriginalConstructor() + ->getMock(); + $this->attrOptionFactory = $this->getMockBuilder( \Magento\Eav\Model\ResourceModel\Entity\Attribute\OptionFactory::class ) @@ -55,6 +90,20 @@ class TableTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMockForAbstractClass(); + $this->sourceMock = $this->getMockBuilder(AbstractSource::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + + $this->abstractAttributeMock = $this->getMockBuilder(AbstractAttribute::class) + ->setMethods( + [ + 'getFrontend', 'getAttributeCode', '__wakeup', 'getStoreId', + 'getId', 'getIsRequired', 'getEntity', 'getBackend' + ] + ) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $this->model = $objectManager->getObject( \Magento\Eav\Model\Entity\Attribute\Source\Table::class, [ @@ -62,6 +111,16 @@ class TableTest extends \PHPUnit_Framework_TestCase 'attrOptionFactory' => $this->attrOptionFactory ] ); + $this->model->setAttribute($this->abstractAttributeMock); + + $this->storeManagerMock = $this->getMockForAbstractClass(StoreManagerInterface::class); + $this->storeMock = $this->getMockForAbstractClass(StoreInterface::class); + + $objectManager->setBackwardCompatibleProperty( + $this->model, + 'storeManager', + $this->storeManagerMock + ); } public function testGetFlatColumns() @@ -74,25 +133,8 @@ class TableTest extends \PHPUnit_Framework_TestCase false ); - $abstractAttributeMock = $this->getMock( - \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class, - ['getFrontend', 'getAttributeCode', '__wakeup'], - [], - '', - false - ); - - $abstractAttributeMock->expects( - $this->any() - )->method( - 'getFrontend' - )->will( - $this->returnValue($abstractFrontendMock) - ); - - $abstractAttributeMock->expects($this->any())->method('getAttributeCode')->will($this->returnValue('code')); - - $this->model->setAttribute($abstractAttributeMock); + $this->abstractAttributeMock->expects($this->any())->method('getFrontend')->willReturn(($abstractFrontendMock)); + $this->abstractAttributeMock->expects($this->any())->method('getAttributeCode')->willReturn('code'); $flatColumns = $this->model->getFlatColumns(); @@ -121,25 +163,16 @@ class TableTest extends \PHPUnit_Framework_TestCase $storeId = 5; $options = [['label' => 'The label', 'value' => 'A value']]; - $attribute = $this->getMock( - \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class, - ['getId', 'getStoreId', 'getIsRequired', '__wakeup'], - [], - '', - false - ); - $attribute->expects($this->once()) + $this->abstractAttributeMock->expects($this->once()) ->method('getId') ->willReturn($attributeId); - $attribute->expects($this->once()) + $this->abstractAttributeMock->expects($this->once()) ->method('getStoreId') ->willReturn($storeId); - $attribute->expects($this->any()) + $this->abstractAttributeMock->expects($this->any()) ->method('getIsRequired') ->willReturn(false); - $this->model->setAttribute($attribute); - $this->collectionFactory->expects($this->once()) ->method('create') ->willReturnSelf(); @@ -191,22 +224,14 @@ class TableTest extends \PHPUnit_Framework_TestCase { $attributeId = 1; $storeId = 5; - $attribute = $this->getMock( - \Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class, - ['getId', 'getStoreId', '__wakeup'], - [], - '', - false - ); - $attribute->expects($this->once()) + + $this->abstractAttributeMock->expects($this->once()) ->method('getId') ->willReturn($attributeId); - $attribute->expects($this->once()) + $this->abstractAttributeMock->expects($this->once()) ->method('getStoreId') ->willReturn($storeId); - $this->model->setAttribute($attribute); - $this->collectionFactory->expects($this->once()) ->method('create') ->willReturnSelf(); @@ -257,16 +282,13 @@ class TableTest extends \PHPUnit_Framework_TestCase ->setMethods([ 'getSelect', 'getStoreId']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $attribute = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class) - ->setMethods(['getAttributeCode', 'getEntity', 'getBackend', 'getId']) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); - $attribute->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); + + $this->abstractAttributeMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode); $entity = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class) ->setMethods(['getLinkField']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $attribute->expects($this->once())->method('getEntity')->willReturn($entity); + $this->abstractAttributeMock->expects($this->once())->method('getEntity')->willReturn($entity); $entity->expects($this->once())->method('getLinkField')->willReturn('entity_id'); $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class) ->setMethods(['joinLeft', 'getConnection', 'order']) @@ -278,9 +300,9 @@ class TableTest extends \PHPUnit_Framework_TestCase ->setMethods(['getTable']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $attribute->expects($this->any())->method('getBackend')->willReturn($backend); + $this->abstractAttributeMock->expects($this->any())->method('getBackend')->willReturn($backend); $backend->expects($this->any())->method('getTable')->willReturn('table_name'); - $attribute->expects($this->any())->method('getId')->willReturn(1); + $this->abstractAttributeMock->expects($this->any())->method('getId')->willReturn(1); $collection->expects($this->once())->method('getStoreId')->willReturn(1); $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class) ->disableOriginalConstructor() @@ -294,11 +316,99 @@ class TableTest extends \PHPUnit_Framework_TestCase ->disableOriginalConstructor() ->getMock(); $this->attrOptionFactory->expects($this->once())->method('create')->willReturn($attrOption); - $attrOption->expects($this->once())->method('addOptionValueToCollection')->with($collection, $attribute, $expr) + $attrOption->expects($this->once())->method('addOptionValueToCollection') + ->with($collection, $this->abstractAttributeMock, $expr) ->willReturnSelf(); $select->expects($this->once())->method('order')->with("{$attributeCode} {$dir}"); - $this->model->setAttribute($attribute); $this->assertEquals($this->model, $this->model->addValueSortToCollection($collection, $dir)); } + + /** + * @param bool $withEmpty + * @param bool $defaultValues + * @param array $options + * @param array $optionsDefault + * @param array $expectedResult + * @dataProvider getAllOptionsDataProvider + */ + public function testGetAllOptions( + $withEmpty, + $defaultValues, + array $options, + array $optionsDefault, + array $expectedResult + ) { + $storeId = '1'; + $attributeId = '42'; + + $this->abstractAttributeMock->expects($this->once())->method('getStoreId')->willReturn(null); + + $this->storeManagerMock->expects($this->once())->method('getStore')->willReturn($this->storeMock); + $this->storeMock->expects($this->once())->method('getId')->willReturn($storeId); + + $this->abstractAttributeMock->expects($this->once())->method('getId')->willReturn($attributeId); + + $this->collectionFactory->expects($this->once()) + ->method('create') + ->willReturnSelf(); + $this->collectionFactory->expects($this->once()) + ->method('setPositionOrder') + ->willReturnSelf(); + $this->collectionFactory->expects($this->once()) + ->method('setAttributeFilter') + ->with($attributeId) + ->willReturnSelf(); + $this->collectionFactory->expects($this->once()) + ->method('setStoreFilter') + ->with($storeId) + ->willReturnSelf(); + $this->collectionFactory->expects($this->once()) + ->method('load') + ->willReturn($this->attributeOptionCollectionMock); + $this->attributeOptionCollectionMock->expects($this->any()) + ->method('toOptionArray') + ->willReturnMap( + [ + ['value', $options], + ['default_value', $optionsDefault] + ] + ); + + $this->assertEquals($expectedResult, $this->model->getAllOptions($withEmpty, $defaultValues)); + } + + /** + * @return array + */ + public function getAllOptionsDataProvider() + { + return [ + [ + false, + false, + [['value' => '16', 'label' => 'black'], ['value' => '17', 'label' => 'white']], + [['value' => '16', 'label' => 'blck'], ['value' => '17', 'label' => 'wht']], + [['value' => '16', 'label' => 'black'], ['value' => '17', 'label' => 'white']] + ], + [ + false, + true, + [['value' => '16', 'label' => 'black'], ['value' => '17', 'label' => 'white']], + [['value' => '16', 'label' => 'blck'], ['value' => '17', 'label' => 'wht']], + [['value' => '16', 'label' => 'blck'], ['value' => '17', 'label' => 'wht']] + ], + [ + true, + false, + [['value' => '16', 'label' => 'black'], ['value' => '17', 'label' => 'white']], + [['value' => '16', 'label' => 'blck'], ['value' => '17', 'label' => 'wht']], + [ + ['label' => ' ', 'value' => ''], + ['value' => '16', 'label' => 'black'], + ['value' => '17', 'label' => 'white'] + ] + ] + ]; + } } diff --git a/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Entity/AttributeTest.php b/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Entity/AttributeTest.php index bcad34654b98aa2d6d3844d1a780d585713c8d86..c18af6380a3b71ca7adfd6af5146f853926b419d 100644 --- a/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Entity/AttributeTest.php +++ b/app/code/Magento/Eav/Test/Unit/Model/ResourceModel/Entity/AttributeTest.php @@ -29,7 +29,7 @@ class AttributeTest extends \PHPUnit_Framework_TestCase { $this->contextMock = $this->getMock( \Magento\Framework\Model\Context::class, - ['getCacheManager', 'getEventDispatcher', 'getLogger', 'getAppState', 'getActionValidator'], + [], [], '', false diff --git a/app/code/Magento/Eav/composer.json b/app/code/Magento/Eav/composer.json index 16e62a6434a26aa358a3491d697ce7cd3bb54ad7..9863a89fff5d3d64ccbf8eaa4b3f5691168e0155 100644 --- a/app/code/Magento/Eav/composer.json +++ b/app/code/Magento/Eav/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-eav", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml index 0fcba9907cc4d46b76cf0b7e86c2e0e1bf61da5a..2de184b01d7c4673d49cf73f5fe0a35afa7149e2 100644 --- a/app/code/Magento/Eav/etc/di.xml +++ b/app/code/Magento/Eav/etc/di.xml @@ -93,9 +93,32 @@ </virtualType> <type name="Magento\Eav\Model\AttributeRepository"> <arguments> - <argument name="collectionProcessor" xsi:type="object">Magento\Framework\Api\SearchCriteria\CollectionProcessor</argument> + <argument name="collectionProcessor" xsi:type="object">Magento\Eav\Model\Api\SearchCriteria\AttributeCollectionProcessor</argument> </arguments> </type> + <virtualType name="Magento\Eav\Model\Api\SearchCriteria\AttributeCollectionProcessor" type="Magento\Framework\Api\SearchCriteria\CollectionProcessor"> + <arguments> + <argument name="processors" xsi:type="array"> + <item name="filters" xsi:type="object">Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor\AttributeFilterProcessor</item> + <item name="sorting" xsi:type="object">Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor\AttributeSortingProcessor</item> + <item name="pagination" xsi:type="object">Magento\Framework\Api\SearchCriteria\CollectionProcessor\PaginationProcessor</item> + </argument> + </arguments> + </virtualType> + <virtualType name="Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor\AttributeFilterProcessor" type="Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor"> + <arguments> + <argument name="fieldMapping" xsi:type="array"> + <item name="attribute_id" xsi:type="string">main_table.attribute_id</item> + </argument> + </arguments> + </virtualType> + <virtualType name="Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor\AttributeSortingProcessor" type="Magento\Framework\Api\SearchCriteria\CollectionProcessor\SortingProcessor"> + <arguments> + <argument name="fieldMapping" xsi:type="array"> + <item name="attribute_id" xsi:type="string">main_table.attribute_id</item> + </argument> + </arguments> + </virtualType> <virtualType name="Magento\Eav\Model\Api\SearchCriteria\CollectionProcessor\AttributeSetFilterProcessor" type="Magento\Framework\Api\SearchCriteria\CollectionProcessor\FilterProcessor"> <arguments> <argument name="customFilters" xsi:type="array"> diff --git a/app/code/Magento/Email/composer.json b/app/code/Magento/Email/composer.json index 33d04b921278d3c57ce0107792c6deef458c73ab..648c984de0bbb6e93d16f84110075dc36909cfda 100644 --- a/app/code/Magento/Email/composer.json +++ b/app/code/Magento/Email/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-email", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-theme": "100.2.*", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", diff --git a/app/code/Magento/EncryptionKey/composer.json b/app/code/Magento/EncryptionKey/composer.json index 6d7ff5558734ab1952b59b2e02db4cc5093f9984..11b1c034a76a792cd782be06db484e494764553f 100644 --- a/app/code/Magento/EncryptionKey/composer.json +++ b/app/code/Magento/EncryptionKey/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-encryption-key", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-backend": "100.2.*", "magento/framework": "100.2.*" diff --git a/app/code/Magento/Fedex/composer.json b/app/code/Magento/Fedex/composer.json index d27d5c1c60d407ce4d1d5b3781b0209c5955a0d4..8d487485c0d210824949657ab7444ba937ef9996 100644 --- a/app/code/Magento/Fedex/composer.json +++ b/app/code/Magento/Fedex/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-fedex", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-shipping": "100.2.*", "magento/module-directory": "100.2.*", diff --git a/app/code/Magento/GiftMessage/composer.json b/app/code/Magento/GiftMessage/composer.json index 04c620dc36db5829f9c8c6aa8a6aec620d211e95..067bf16017b231977183491ff6a4e8cdf91dfe68 100644 --- a/app/code/Magento/GiftMessage/composer.json +++ b/app/code/Magento/GiftMessage/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-gift-message", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-checkout": "100.2.*", diff --git a/app/code/Magento/GoogleAdwords/composer.json b/app/code/Magento/GoogleAdwords/composer.json index ad3df5c0b8b2621f395acbfe81bfde3c6b7bae27..cacbbe2088c778ff4a3777f129aea263cf9dee79 100644 --- a/app/code/Magento/GoogleAdwords/composer.json +++ b/app/code/Magento/GoogleAdwords/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-google-adwords", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-sales": "100.2.*", "magento/framework": "100.2.*" diff --git a/app/code/Magento/GoogleAnalytics/composer.json b/app/code/Magento/GoogleAnalytics/composer.json index 4cccc7bd0cbff69369ae852138045ed06a0dbf4a..8653e93d7a98338160895c65011cdc2f7471270f 100644 --- a/app/code/Magento/GoogleAnalytics/composer.json +++ b/app/code/Magento/GoogleAnalytics/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-google-analytics", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-sales": "100.2.*", "magento/framework": "100.2.*", diff --git a/app/code/Magento/GoogleOptimizer/composer.json b/app/code/Magento/GoogleOptimizer/composer.json index 20a48fc78060b53fb144aa5da405b343ce696063..8c61e9f068ea122b74a7dea02ed5c110d26629b6 100644 --- a/app/code/Magento/GoogleOptimizer/composer.json +++ b/app/code/Magento/GoogleOptimizer/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-google-optimizer", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-google-analytics": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/GroupedImportExport/composer.json b/app/code/Magento/GroupedImportExport/composer.json index b036a8d5d528bd3959ea66b294d7b51736f7ba2c..50c061e5b56da30c1faf2bfab9439460be6908a3 100644 --- a/app/code/Magento/GroupedImportExport/composer.json +++ b/app/code/Magento/GroupedImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-grouped-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-import-export": "100.2.*", "magento/module-catalog-import-export": "100.2.*", diff --git a/app/code/Magento/GroupedProduct/composer.json b/app/code/Magento/GroupedProduct/composer.json index 69aed214284454722708703068de37e3134393f0..0f207eb414789becda0c4e57846f0a48ced13d7a 100644 --- a/app/code/Magento/GroupedProduct/composer.json +++ b/app/code/Magento/GroupedProduct/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-grouped-product", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-catalog-inventory": "100.2.*", diff --git a/app/code/Magento/ImportExport/composer.json b/app/code/Magento/ImportExport/composer.json index 35a088cbd9c91907c3f9f2c434f16cfb86ed57a8..5e403d2180652a6f3565bf03b573abf75310f19f 100644 --- a/app/code/Magento/ImportExport/composer.json +++ b/app/code/Magento/ImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/Indexer/composer.json b/app/code/Magento/Indexer/composer.json index cbaab12f6ee285ac1207f8c01d5ed5ff5af42c07..e80d8a9f7bc90d63dfbbced39431571d73fdc937 100644 --- a/app/code/Magento/Indexer/composer.json +++ b/app/code/Magento/Indexer/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-indexer", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/app/code/Magento/Integration/composer.json b/app/code/Magento/Integration/composer.json index 028e56472268abc0e0fcc612107a5bd8072da806..f76d60c957c87d828cc773e96a5edf10266293c9 100644 --- a/app/code/Magento/Integration/composer.json +++ b/app/code/Magento/Integration/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-integration", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/LayeredNavigation/composer.json b/app/code/Magento/LayeredNavigation/composer.json index 99299c3e4a32cccd465af4131bc549b033142545..d1d82781bb5c881cceaaf0b8ccdf85fa02c54e86 100644 --- a/app/code/Magento/LayeredNavigation/composer.json +++ b/app/code/Magento/LayeredNavigation/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-layered-navigation", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-catalog": "101.1.*", "magento/framework": "100.2.*" diff --git a/app/code/Magento/Marketplace/composer.json b/app/code/Magento/Marketplace/composer.json index 2916ff94544bcc46cd59a9e48e9385e89ea6283b..ed79a80bc0c35d81efa3f27867b33846e3a19007 100644 --- a/app/code/Magento/Marketplace/composer.json +++ b/app/code/Magento/Marketplace/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-marketplace", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*", "magento/module-backend": "100.2.*" }, diff --git a/app/code/Magento/MediaStorage/composer.json b/app/code/Magento/MediaStorage/composer.json index fa7858d5b0aff32de7d37f2756aca1891cb15cf7..141e1c19c532cc67f8152459de7e12d50fb0b9d8 100644 --- a/app/code/Magento/MediaStorage/composer.json +++ b/app/code/Magento/MediaStorage/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-media-storage", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-config": "100.2.*", diff --git a/app/code/Magento/Msrp/composer.json b/app/code/Magento/Msrp/composer.json index b86374e6be2779d14fd3d2b3e8a4d5b46b1b881f..422aac99869919aa08c31e7ac4edd39d9e0a6f2e 100644 --- a/app/code/Magento/Msrp/composer.json +++ b/app/code/Magento/Msrp/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-msrp", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-downloadable": "100.2.*", diff --git a/app/code/Magento/Multishipping/composer.json b/app/code/Magento/Multishipping/composer.json index 8d9ac91cd05a07b78c19588cbcd34ce3a110cc9f..970c7ea0569a28d226f8f50c419b3d5ee998a17f 100644 --- a/app/code/Magento/Multishipping/composer.json +++ b/app/code/Magento/Multishipping/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-multishipping", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-checkout": "100.2.*", "magento/module-sales": "100.2.*", diff --git a/app/code/Magento/NewRelicReporting/composer.json b/app/code/Magento/NewRelicReporting/composer.json index 9faa17d454e5072f7dc9ec5473057bfad1533dce..097de6bea926c60644bedefec56adde409f5aa04 100644 --- a/app/code/Magento/NewRelicReporting/composer.json +++ b/app/code/Magento/NewRelicReporting/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-new-relic-reporting", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Newsletter/composer.json b/app/code/Magento/Newsletter/composer.json index 06a278aef6d48692c856f56daf213e4bab162db8..ab5cdd1510a35285d42698ee999737a74b699d54 100644 --- a/app/code/Magento/Newsletter/composer.json +++ b/app/code/Magento/Newsletter/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-newsletter", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-customer": "100.2.*", "magento/module-widget": "100.2.*", diff --git a/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml b/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml index 2daee81846c0bfb211aeb453e5727d4a130cb77c..f1b8340b1b88d1d5b519da4305f44992015948b8 100644 --- a/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Newsletter/view/frontend/layout/customer_account.xml @@ -8,10 +8,11 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-newsletter-subscriptions-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-newsletter-subscriptions-link"> <arguments> <argument name="path" xsi:type="string">newsletter/manage</argument> <argument name="label" xsi:type="string" translate="true">Newsletter Subscriptions</argument> + <argument name="sortOrder" xsi:type="number">40</argument> </arguments> </block> </referenceBlock> diff --git a/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml b/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml index fb6989716cd2dfc64d1e97c487e77f3b47afeb35..0ee371c32817649669fe2bc20d88b08e132a9a11 100644 --- a/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml +++ b/app/code/Magento/Newsletter/view/frontend/templates/subscribe.phtml @@ -10,7 +10,7 @@ ?> <div class="block newsletter"> - <div class="title"><strong>Newsletter</strong></div> + <div class="title"><strong><?php /* @escapeNotVerified */ echo __('Newsletter') ?></strong></div> <div class="content"> <form class="form subscribe" novalidate diff --git a/app/code/Magento/OfflinePayments/Block/Info/Checkmo.php b/app/code/Magento/OfflinePayments/Block/Info/Checkmo.php index 84b6e554d4cfb193e1418f3d9460f9fcc44f6a7d..8616c186555e3e71db0a13a13c648b5c20ff3e0c 100644 --- a/app/code/Magento/OfflinePayments/Block/Info/Checkmo.php +++ b/app/code/Magento/OfflinePayments/Block/Info/Checkmo.php @@ -49,20 +49,13 @@ class Checkmo extends \Magento\Payment\Block\Info } /** - * Enter description here... - * + * @deprecated * @return $this */ protected function _convertAdditionalData() { - $details = @unserialize($this->getInfo()->getAdditionalData()); - if (is_array($details)) { - $this->_payableTo = isset($details['payable_to']) ? (string)$details['payable_to'] : ''; - $this->_mailingAddress = isset($details['mailing_address']) ? (string)$details['mailing_address'] : ''; - } else { - $this->_payableTo = ''; - $this->_mailingAddress = ''; - } + $this->_payableTo = $this->getInfo()->getAdditionalInformation('payable_to'); + $this->_mailingAddress = $this->getInfo()->getAdditionalInformation('mailing_address'); return $this; } diff --git a/app/code/Magento/OfflinePayments/Test/Unit/Block/Info/CheckmoTest.php b/app/code/Magento/OfflinePayments/Test/Unit/Block/Info/CheckmoTest.php index 848cfbae52b25a0b4b202b296f43ae4b79c5c19a..27209fc8d3538a1465e985929ef6adb52408de93 100644 --- a/app/code/Magento/OfflinePayments/Test/Unit/Block/Info/CheckmoTest.php +++ b/app/code/Magento/OfflinePayments/Test/Unit/Block/Info/CheckmoTest.php @@ -5,81 +5,118 @@ */ namespace Magento\OfflinePayments\Test\Unit\Block\Info; +use Magento\Framework\View\Element\Template\Context; +use Magento\OfflinePayments\Block\Info\Checkmo; +use Magento\Payment\Model\Info; +use PHPUnit_Framework_MockObject_MockObject as MockObject; + +/** + * CheckmoTest contains list of test for block methods testing + */ class CheckmoTest extends \PHPUnit_Framework_TestCase { /** - * @var \Magento\OfflinePayments\Block\Info\Checkmo + * @var Info|MockObject + */ + private $info; + + /** + * @var Checkmo */ - protected $_model; + private $block; + /** + * @inheritdoc + */ protected function setUp() { - $context = $this->getMock(\Magento\Framework\View\Element\Template\Context::class, [], [], '', false); - $this->_model = new \Magento\OfflinePayments\Block\Info\Checkmo($context); + $context = $this->getMockBuilder(Context::class) + ->disableOriginalConstructor() + ->setMethods([]) + ->getMock(); + + $this->info = $this->getMockBuilder(Info::class) + ->disableOriginalConstructor() + ->setMethods(['getAdditionalInformation']) + ->getMock(); + + $this->block = new Checkmo($context); } /** + * @covers \Magento\OfflinePayments\Block\Info\Checkmo::getPayableTo + * @param array $details + * @param string|null $expected * @dataProvider getPayableToDataProvider */ public function testGetPayableTo($details, $expected) { - $info = $this->getMock(\Magento\Payment\Model\Info::class, ['getAdditionalData'], [], '', false); - $info->expects($this->once()) - ->method('getAdditionalData') - ->willReturn(serialize($details)); - $this->_model->setData('info', $info); + $this->info->expects(static::at(0)) + ->method('getAdditionalInformation') + ->with('payable_to') + ->willReturn($details); + $this->block->setData('info', $this->info); - $this->assertEquals($expected, $this->_model->getPayableTo()); + static::assertEquals($expected, $this->block->getPayableTo()); } /** + * Get list of variations for payable configuration option testing * @return array */ public function getPayableToDataProvider() { return [ - [['payable_to' => 'payable'], 'payable'], - ['', ''] + ['payable_to' => 'payable', 'payable'], + ['', null] ]; } /** + * @covers \Magento\OfflinePayments\Block\Info\Checkmo::getMailingAddress + * @param array $details + * @param string|null $expected * @dataProvider getMailingAddressDataProvider */ public function testGetMailingAddress($details, $expected) { - $info = $this->getMock(\Magento\Payment\Model\Info::class, ['getAdditionalData'], [], '', false); - $info->expects($this->once()) - ->method('getAdditionalData') - ->willReturn(serialize($details)); - $this->_model->setData('info', $info); + $this->info->expects(static::at(1)) + ->method('getAdditionalInformation') + ->with('mailing_address') + ->willReturn($details); + $this->block->setData('info', $this->info); - $this->assertEquals($expected, $this->_model->getMailingAddress()); + static::assertEquals($expected, $this->block->getMailingAddress()); } /** + * Get list of variations for mailing address testing * @return array */ public function getMailingAddressDataProvider() { return [ - [['mailing_address' => 'blah@blah.com'], 'blah@blah.com'], - ['', ''] + ['mailing_address' => 'blah@blah.com', 'blah@blah.com'], + ['mailing_address' => '', null] ]; } + /** + * @covers \Magento\OfflinePayments\Block\Info\Checkmo::getMailingAddress + */ public function testConvertAdditionalDataIsNeverCalled() { - $info = $this->getMock(\Magento\Payment\Model\Info::class, ['getAdditionalData'], [], '', false); - $info->expects($this->once()) - ->method('getAdditionalData') - ->willReturn(serialize(['mailing_address' => 'blah@blah.com'])); - $this->_model->setData('info', $info); + $mailingAddress = 'blah@blah.com'; + $this->info->expects(static::at(1)) + ->method('getAdditionalInformation') + ->with('mailing_address') + ->willReturn($mailingAddress); + $this->block->setData('info', $this->info); // First we set the property $this->_mailingAddress - $this->_model->getMailingAddress(); + $this->block->getMailingAddress(); // And now we get already setted property $this->_mailingAddress - $this->assertEquals('blah@blah.com', $this->_model->getMailingAddress()); + static::assertEquals($mailingAddress, $this->block->getMailingAddress()); } } diff --git a/app/code/Magento/OfflinePayments/composer.json b/app/code/Magento/OfflinePayments/composer.json index 52deb08ba3d904a824faa762391ddf67ae29b026..cf728aaa1c5f6a7ea7c36163b021cb378d7df7f0 100644 --- a/app/code/Magento/OfflinePayments/composer.json +++ b/app/code/Magento/OfflinePayments/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-offline-payments", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-checkout": "100.2.*", "magento/module-payment": "100.2.*", "magento/framework": "100.2.*" diff --git a/app/code/Magento/OfflinePayments/view/adminhtml/templates/info/checkmo.phtml b/app/code/Magento/OfflinePayments/view/adminhtml/templates/info/checkmo.phtml index d5bff77e002d17d5ad8a80349ea646aee0135fa4..8c5cfd50cc110f2f8c61414c14ed2d48965929d6 100644 --- a/app/code/Magento/OfflinePayments/view/adminhtml/templates/info/checkmo.phtml +++ b/app/code/Magento/OfflinePayments/view/adminhtml/templates/info/checkmo.phtml @@ -10,7 +10,7 @@ */ ?> <?php echo $block->escapeHtml($block->getMethod()->getTitle()) ?> -<?php if ($block->getInfo()->getAdditionalData()): ?> +<?php if ($block->getInfo()->getAdditionalInformation()): ?> <?php if ($block->getPayableTo()): ?> <br /><?php echo $block->escapeHtml(__('Make Check payable to: %1', $block->getPayableTo())) ?> <?php endif; ?> diff --git a/app/code/Magento/OfflinePayments/view/adminhtml/templates/info/pdf/checkmo.phtml b/app/code/Magento/OfflinePayments/view/adminhtml/templates/info/pdf/checkmo.phtml index 6195cdbd776548c229a6d1dfca03c8f4c1b2a61f..5587ac239d37e84a21348cfd994d9d0b73f0ba97 100644 --- a/app/code/Magento/OfflinePayments/view/adminhtml/templates/info/pdf/checkmo.phtml +++ b/app/code/Magento/OfflinePayments/view/adminhtml/templates/info/pdf/checkmo.phtml @@ -11,7 +11,7 @@ ?> <?php echo $block->escapeHtml($block->getMethod()->getTitle()) ?> {{pdf_row_separator}} -<?php if ($block->getInfo()->getAdditionalData()): ?> +<?php if ($block->getInfo()->getAdditionalInformation()): ?> {{pdf_row_separator}} <?php if ($block->getPayableTo()): ?> <?php echo $block->escapeHtml(__('Make Check payable to: %1', $block->getPayableTo())) ?> diff --git a/app/code/Magento/OfflinePayments/view/frontend/templates/info/checkmo.phtml b/app/code/Magento/OfflinePayments/view/frontend/templates/info/checkmo.phtml index f0dbff1add32b2fd7d49e3b6ca5f14c4038fc2ca..3c0b6bb23086a67b48de1134cdb245daffeab045 100644 --- a/app/code/Magento/OfflinePayments/view/frontend/templates/info/checkmo.phtml +++ b/app/code/Magento/OfflinePayments/view/frontend/templates/info/checkmo.phtml @@ -11,7 +11,7 @@ ?> <dl class="payment-method checkmemo"> <dt class="title"><?php echo $block->escapeHtml($block->getMethod()->getTitle()) ?></dt> - <?php if ($block->getInfo()->getAdditionalData()): ?> + <?php if ($block->getInfo()->getAdditionalInformation()): ?> <?php if ($block->getPayableTo()): ?> <dd class="content"> <strong><?php echo $block->escapeHtml(__('Make Check payable to')) ?></strong> diff --git a/app/code/Magento/OfflineShipping/composer.json b/app/code/Magento/OfflineShipping/composer.json index 3c959b4dec766fe09cf81df46571051b714ee4e1..b429376c6cd7adc0a70b133a633b4edf068d684e 100644 --- a/app/code/Magento/OfflineShipping/composer.json +++ b/app/code/Magento/OfflineShipping/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-offline-shipping", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", diff --git a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml index 50ed4cdc9417833683a04e3a715ff828f04b9674..4a4f550588f5366668f4ceee0ff895964246172b 100644 --- a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml +++ b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml @@ -52,6 +52,7 @@ <field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Show Method if Not Applicable</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <frontend_class>shipping-skip-hide</frontend_class> </field> <field id="specificerrmsg" translate="label" type="textarea" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Displayed Error Message</label> @@ -146,6 +147,7 @@ <field id="showmethod" translate="label" type="select" sortOrder="92" showInDefault="1" showInWebsite="1" showInStore="0"> <label>Show Method if Not Applicable</label> <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <frontend_class>shipping-skip-hide</frontend_class> </field> <field id="specificerrmsg" translate="label" type="textarea" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1"> <label>Displayed Error Message</label> diff --git a/app/code/Magento/PageCache/Model/Config.php b/app/code/Magento/PageCache/Model/Config.php index 222d9d57e467a31988fd6b6ac221f7ce640c06c5..786165728406f0162be0da04f869a10020d9d3bb 100644 --- a/app/code/Magento/PageCache/Model/Config.php +++ b/app/code/Magento/PageCache/Model/Config.php @@ -148,12 +148,17 @@ class Config ), '/* {{ ips }} */' => $this->_getAccessList(), '/* {{ design_exceptions_code }} */' => $this->_getDesignExceptions(), - // http headers get transformed by php `X-Forwarded-Proto: https` becomes $SERVER['HTTP_X_FORWARDED_PROTO'] = 'https' + // http headers get transformed by php `X-Forwarded-Proto: https` + // becomes $SERVER['HTTP_X_FORWARDED_PROTO'] = 'https' // Apache and Nginx drop all headers with underlines by default. - '/* {{ ssl_offloaded_header }} */' => str_replace('_', '-', $this->_scopeConfig->getValue( - \Magento\Framework\HTTP\PhpEnvironment\Request::XML_PATH_OFFLOADER_HEADER, - \Magento\Store\Model\ScopeInterface::SCOPE_STORE)) - + '/* {{ ssl_offloaded_header }} */' => str_replace( + '_', + '-', + $this->_scopeConfig->getValue( + \Magento\Framework\HTTP\PhpEnvironment\Request::XML_PATH_OFFLOADER_HEADER, + \Magento\Store\Model\ScopeInterface::SCOPE_STORE + ) + ) ]; } @@ -176,6 +181,7 @@ class Config \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); if (!empty($accessList)) { + $result = []; $ips = explode(',', $accessList); foreach ($ips as $ip) { $result[] = sprintf($tpl, trim($ip)); diff --git a/app/code/Magento/PageCache/composer.json b/app/code/Magento/PageCache/composer.json index 5c29e4fc3c97081626ae4a77243ccd6ac9843d38..f18eb7bbe94edd31a48c4796dc41b21cdef38a8b 100644 --- a/app/code/Magento/PageCache/composer.json +++ b/app/code/Magento/PageCache/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-page-cache", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", diff --git a/app/code/Magento/PageCache/etc/events.xml b/app/code/Magento/PageCache/etc/events.xml index 7ac67a931c7ac8bbbe3b5e397a7ade7b3a039633..8d88feeab18bca0ef4101933f54335d51eed2129 100644 --- a/app/code/Magento/PageCache/etc/events.xml +++ b/app/code/Magento/PageCache/etc/events.xml @@ -48,11 +48,6 @@ <event name="model_save_commit_after"> <observer name="flush_cache_after_model_save" instance="Magento\PageCache\Observer\FlushCacheByTags" /> </event> - <event name="controller_action_predispatch"> - <observer name="register_form_key" - instance="Magento\PageCache\Observer\RegisterFormKeyFromCookie" - /> - </event> <event name="customer_logout"> <observer name="FlushFormKeyOnLogout" instance="Magento\PageCache\Observer\FlushFormKeyOnLogout"/> </event> diff --git a/app/code/Magento/PageCache/etc/frontend/events.xml b/app/code/Magento/PageCache/etc/frontend/events.xml new file mode 100644 index 0000000000000000000000000000000000000000..10ef2eee02804fea57163ea4103cede60a01d220 --- /dev/null +++ b/app/code/Magento/PageCache/etc/frontend/events.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. 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="controller_action_predispatch"> + <observer name="register_form_key" instance="Magento\PageCache\Observer\RegisterFormKeyFromCookie" /> + </event> +</config> diff --git a/app/code/Magento/Payment/composer.json b/app/code/Magento/Payment/composer.json index 35ff326d916bd43f8ae2366d9b50f5b77fe46dc8..c8d7254ce9e002ced1ecfb1067d821c63dbeb5be 100644 --- a/app/code/Magento/Payment/composer.json +++ b/app/code/Magento/Payment/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-payment", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-sales": "100.2.*", diff --git a/app/code/Magento/Paypal/Model/Payflow/Transparent.php b/app/code/Magento/Paypal/Model/Payflow/Transparent.php index b5803c5ace3925b75e81df43992f714a74ed9905..f65edd71daa5b82b5191cd20a7f01a4b90b79b8c 100644 --- a/app/code/Magento/Paypal/Model/Payflow/Transparent.php +++ b/app/code/Magento/Paypal/Model/Payflow/Transparent.php @@ -186,6 +186,7 @@ class Transparent extends Payflowpro implements TransparentInterface $this->createPaymentToken($payment, $token); $payment->unsAdditionalInformation(self::CC_DETAILS); + $payment->unsAdditionalInformation(self::PNREF); return $this; } diff --git a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php index 2d757147cec6a89dc1b6b6b28f283fd29b98f640..e2d5d348dda2cf96da1789806a43814c8960c49a 100644 --- a/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Model/Payflow/TransparentTest.php @@ -428,6 +428,13 @@ class TransparentTest extends \PHPUnit_Framework_TestCase ->method('setVaultPaymentToken') ->with($paymentTokenMock); + $this->paymentMock->expects($this->at(8)) + ->method('unsAdditionalInformation') + ->with(Transparent::CC_DETAILS); + $this->paymentMock->expects($this->at(9)) + ->method('unsAdditionalInformation') + ->with(Transparent::PNREF); + $this->assertSame($this->object, $this->object->authorize($this->paymentMock, 33)); } } diff --git a/app/code/Magento/Paypal/composer.json b/app/code/Magento/Paypal/composer.json index bddc5d91a94587f4d26532246d8c83f8de6ba7f7..7503db18f008ca76252f8687df6d52f88dc0e02d 100644 --- a/app/code/Magento/Paypal/composer.json +++ b/app/code/Magento/Paypal/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-paypal", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-checkout": "100.2.*", diff --git a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml index 1876f07830ba7b55df25230bc256b829f65b2fc3..90cb60186e34e3730ae6d068b34e4002add00a6c 100644 --- a/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml +++ b/app/code/Magento/Paypal/etc/adminhtml/system/express_checkout.xml @@ -23,7 +23,8 @@ <label>Express Checkout</label> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> <field id="business_account" translate="label comment tooltip" showInDefault="1" showInWebsite="1" sortOrder="5"> - <label>Email Associated with PayPal Merchant Account</label> + <label>Email Associated with PayPal Merchant Account (Optional)</label> + <frontend_class>not-required</frontend_class> <comment> <![CDATA[<a href="http://www.magentocommerce.com/paypal">Start accepting payments via PayPal!</a>]]> </comment> diff --git a/app/code/Magento/Paypal/view/frontend/layout/customer_account.xml b/app/code/Magento/Paypal/view/frontend/layout/customer_account.xml index 5a355a2c399673ec54e1da9fed67f281c32fa1ab..27fa1511d09be68d350ad74a92146c928cff631c 100644 --- a/app/code/Magento/Paypal/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Paypal/view/frontend/layout/customer_account.xml @@ -11,10 +11,11 @@ </head> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-billing-agreements-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-billing-agreements-link"> <arguments> <argument name="path" xsi:type="string">paypal/billing_agreement</argument> <argument name="label" xsi:type="string" translate="true">Billing Agreements</argument> + <argument name="sortOrder" xsi:type="number">140</argument> </arguments> </block> </referenceBlock> diff --git a/app/code/Magento/Persistent/composer.json b/app/code/Magento/Persistent/composer.json index 25cc9d76acd768fe970363628abad60945e418d9..61f77a104c016d23f3734433efd8646bd21b42f4 100644 --- a/app/code/Magento/Persistent/composer.json +++ b/app/code/Magento/Persistent/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-persistent", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-checkout": "100.2.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Persistent/etc/persistent.xml b/app/code/Magento/Persistent/etc/persistent.xml index fb67e9d976af40adb4b95a5ba145a7d175995e5d..b4b87cb8e765df3e0dab3dad4a1f5b08fd414209 100644 --- a/app/code/Magento/Persistent/etc/persistent.xml +++ b/app/code/Magento/Persistent/etc/persistent.xml @@ -18,7 +18,7 @@ <name_in_layout>top.links</name_in_layout> <class>Magento\Persistent\Model\Observer</class> <method>emulateTopLinks</method> - <block_type>Magento\Theme\Block\Template\Links</block_type> + <block_type>Magento\Customer\Block\Account\Navigation</block_type> </reference> </blocks> </instances> diff --git a/app/code/Magento/ProductAlert/composer.json b/app/code/Magento/ProductAlert/composer.json index a43a52209740318e0f5adcc8bea03a8860c04037..9c63c6958a465852ea3148cab659102880b07630 100644 --- a/app/code/Magento/ProductAlert/composer.json +++ b/app/code/Magento/ProductAlert/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-product-alert", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php b/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php index f0edccbaf812acbf8495d97817515395635af409..7a087f769877160a316635baf9017b8b37ebbd38 100644 --- a/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php +++ b/app/code/Magento/ProductVideo/Controller/Adminhtml/Product/Gallery/RetrieveImage.php @@ -6,6 +6,7 @@ namespace Magento\ProductVideo\Controller\Adminhtml\Product\Gallery; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\File\Uploader; /** @@ -43,6 +44,13 @@ class RetrieveImage extends \Magento\Backend\App\Action */ protected $fileUtility; + /** + * URI validator + * + * @var \Magento\Framework\Validator\ValidatorInterface + */ + private $protocolValidator; + /** * @param \Magento\Backend\App\Action\Context $context * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory @@ -51,6 +59,7 @@ class RetrieveImage extends \Magento\Backend\App\Action * @param \Magento\Framework\Image\AdapterFactory $imageAdapterFactory * @param \Magento\Framework\HTTP\Adapter\Curl $curl * @param \Magento\MediaStorage\Model\ResourceModel\File\Storage\File $fileUtility + * @param \Magento\Framework\Validator\ValidatorInterface $protocolValidator */ public function __construct( \Magento\Backend\App\Action\Context $context, @@ -59,7 +68,8 @@ class RetrieveImage extends \Magento\Backend\App\Action \Magento\Framework\Filesystem $fileSystem, \Magento\Framework\Image\AdapterFactory $imageAdapterFactory, \Magento\Framework\HTTP\Adapter\Curl $curl, - \Magento\MediaStorage\Model\ResourceModel\File\Storage\File $fileUtility + \Magento\MediaStorage\Model\ResourceModel\File\Storage\File $fileUtility, + \Magento\Framework\Validator\ValidatorInterface $protocolValidator = null ) { parent::__construct($context); $this->resultRawFactory = $resultRawFactory; @@ -68,6 +78,10 @@ class RetrieveImage extends \Magento\Backend\App\Action $this->imageAdapter = $imageAdapterFactory->create(); $this->curl = $curl; $this->fileUtility = $fileUtility; + + $this->protocolValidator = $protocolValidator ?: + \Magento\Framework\App\ObjectManager::getInstance() + ->get(\Magento\Framework\Validator\ValidatorInterface::class); } /** @@ -78,15 +92,15 @@ class RetrieveImage extends \Magento\Backend\App\Action $baseTmpMediaPath = $this->mediaConfig->getBaseTmpMediaPath(); try { $remoteFileUrl = $this->getRequest()->getParam('remote_image'); - $originalFileName = basename($remoteFileUrl); - $localFileName = Uploader::getCorrectFileName($originalFileName); + $this->validateRemoteFile($remoteFileUrl); + $localFileName = Uploader::getCorrectFileName(basename($remoteFileUrl)); $localTmpFileName = Uploader::getDispretionPath($localFileName) . DIRECTORY_SEPARATOR . $localFileName; - $localFileMediaPath = $baseTmpMediaPath . ($localTmpFileName); - $localUniqueFileMediaPath = $this->appendNewFileName($localFileMediaPath); - $this->retrieveRemoteImage($remoteFileUrl, $localUniqueFileMediaPath); - $localFileFullPath = $this->appendAbsoluteFileSystemPath($localUniqueFileMediaPath); + $localFilePath = $baseTmpMediaPath . ($localTmpFileName); + $localUniqFilePath = $this->appendNewFileName($localFilePath); + $this->retrieveRemoteImage($remoteFileUrl, $localUniqFilePath); + $localFileFullPath = $this->appendAbsoluteFileSystemPath($localUniqFilePath); $this->imageAdapter->validateUploadFile($localFileFullPath); - $result = $this->appendResultSaveRemoteImage($localUniqueFileMediaPath); + $result = $this->appendResultSaveRemoteImage($localUniqFilePath); } catch (\Exception $e) { $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; } @@ -98,6 +112,25 @@ class RetrieveImage extends \Magento\Backend\App\Action return $response; } + /** + * Validate remote file + * + * @param string $remoteFileUrl + * @throws LocalizedException + * + * @return $this + */ + private function validateRemoteFile($remoteFileUrl) + { + if (!$this->protocolValidator->isValid($remoteFileUrl)) { + throw new LocalizedException( + __("Protocol isn't allowed") + ); + } + + return $this; + } + /** * @param string $fileName * @return mixed @@ -116,6 +149,8 @@ class RetrieveImage extends \Magento\Backend\App\Action } /** + * Trying to get remote image to save it locally + * * @param string $fileUrl * @param string $localFilePath * @return void @@ -127,7 +162,7 @@ class RetrieveImage extends \Magento\Backend\App\Action $this->curl->write('GET', $fileUrl); $image = $this->curl->read(); if (empty($image)) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __('Could not get preview image information. Please check your connection and try again.') ); } diff --git a/app/code/Magento/ProductVideo/Test/Unit/Controller/Adminhtml/Product/Gallery/RetrieveImageTest.php b/app/code/Magento/ProductVideo/Test/Unit/Controller/Adminhtml/Product/Gallery/RetrieveImageTest.php index 79d83f38421bba1a2e582efc3bc90f261c8b79dc..64149d529e5ca8f993e300594279a7e0842f9b8b 100644 --- a/app/code/Magento/ProductVideo/Test/Unit/Controller/Adminhtml/Product/Gallery/RetrieveImageTest.php +++ b/app/code/Magento/ProductVideo/Test/Unit/Controller/Adminhtml/Product/Gallery/RetrieveImageTest.php @@ -73,17 +73,11 @@ class RetrieveImageTest extends \PHPUnit_Framework_TestCase */ protected function setUp() { + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->contextMock = $this->getMock(\Magento\Backend\App\Action\Context::class, [], [], '', false); $this->rawFactoryMock = $this->getMock(\Magento\Framework\Controller\Result\RawFactory::class, ['create'], [], '', false); - $response = - $this->getMock( - \Magento\Framework\Controller\Result\Raw::class, - [], - [], - '', - false - ); + $response = new \Magento\Framework\DataObject(); $this->rawFactoryMock->expects($this->once())->method('create')->willReturn($response); $this->configMock = $this->getMock(\Magento\Catalog\Model\Product\Media\Config::class, [], [], '', false); $this->filesystemMock = $this->getMock(\Magento\Framework\Filesystem::class, [], [], '', false); @@ -101,11 +95,15 @@ class RetrieveImageTest extends \PHPUnit_Framework_TestCase $this->adapterFactoryMock->expects($this->once())->method('create')->willReturn($this->abstractAdapter); $this->curlMock = $this->getMock(\Magento\Framework\HTTP\Adapter\Curl::class, [], [], '', false); $this->storageFileMock = - $this->getMock(\Magento\MediaStorage\Model\ResourceModel\File\Storage\File::class, [], [], '', false); + $this->getMock(\Magento\MediaStorage\Model\ResourceModel\File\Storage\File::class, [], [], '', false); $this->request = $this->getMock(\Magento\Framework\App\RequestInterface::class); $this->contextMock->expects($this->any())->method('getRequest')->will($this->returnValue($this->request)); - - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $managerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class) + ->disableOriginalConstructor() + ->setMethods(['get']) + ->getMockForAbstractClass(); + $this->contextMock->expects($this->any())->method('getRequest')->will($this->returnValue($this->request)); + $this->contextMock->expects($this->any())->method('getObjectManager')->willReturn($managerMock); $this->image = $objectManager->getObject( \Magento\ProductVideo\Controller\Adminhtml\Product\Gallery\RetrieveImage::class, @@ -117,6 +115,7 @@ class RetrieveImageTest extends \PHPUnit_Framework_TestCase 'imageAdapterFactory' => $this->adapterFactoryMock, 'curl' => $this->curlMock, 'fileUtility' => $this->storageFileMock, + 'protocolValidator' => new \Magento\Framework\Validator\AllowedProtocols(), ] ); } diff --git a/app/code/Magento/ProductVideo/composer.json b/app/code/Magento/ProductVideo/composer.json index 362fd006d2690c9f9b60b7be3abb71d5ce0b4da7..08d21e4c2abbfb167aed2ca24c0ba012d6b2e8c8 100644 --- a/app/code/Magento/ProductVideo/composer.json +++ b/app/code/Magento/ProductVideo/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-product-video", "description": "Add Video to Products", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-backend": "100.2.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/ProductVideo/etc/di.xml b/app/code/Magento/ProductVideo/etc/di.xml index 09c15d8fccecc64d3dd183e7940aa26b6819d6df..7242a9d48ce1e3c0da66a3f4d5953cda705132cd 100644 --- a/app/code/Magento/ProductVideo/etc/di.xml +++ b/app/code/Magento/ProductVideo/etc/di.xml @@ -7,6 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <preference for="Magento\Framework\Api\Data\VideoContentInterface" type="Magento\ProductVideo\Model\Product\Attribute\Media\VideoEntry" /> + <preference for="Magento\Framework\Validator\ValidatorInterface" type="Magento\Framework\Validator\AllowedProtocols" /> <type name="Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool"> <arguments> <argument name="mediaGalleryEntryConvertersCollection" xsi:type="array"> diff --git a/app/code/Magento/ProductVideo/i18n/en_US.csv b/app/code/Magento/ProductVideo/i18n/en_US.csv index 4cfabe1592dd2d0ce5c8072f0bdaafbc847689e6..64a08075c19548bc092f3918e7dabf606dd2052e 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 "Show related video","Show related video" "Auto restart video","Auto restart video" "Images And Videos","Images And Videos" +"Protocol isn't allowed", "Protocol isn't allowed" diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php index 3cdcf70cf24266ae7613e7e6f790c4b6777ff23b..f84ca33b436f91e4afca6cae6f52c8b975cf4289 100644 --- a/app/code/Magento/Quote/Model/Quote.php +++ b/app/code/Magento/Quote/Model/Quote.php @@ -2157,6 +2157,12 @@ class Quote extends AbstractExtensibleModel implements \Magento\Quote\Api\Data\C { if (!$this->getReservedOrderId()) { $this->setReservedOrderId($this->_getResource()->getReservedOrderId($this)); + } else { + //checking if reserved order id was already used for some order + //if yes reserving new one if not using old one + if ($this->_getResource()->isOrderIncrementIdUsed($this->getReservedOrderId())) { + $this->setReservedOrderId($this->_getResource()->getReservedOrderId($this)); + } } return $this; } diff --git a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php index f1660c9f9d6427fb617c2de9d87ae2a8026be3aa..7d8946661476cb851f2488405708847530e99673 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php +++ b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php @@ -59,10 +59,8 @@ class Shipping extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal $addressWeight = $address->getWeight(); $freeMethodWeight = $address->getFreeMethodWeight(); + $addressFreeShipping = $address->getFreeShipping(); - $address->setFreeShipping( - $this->freeShipping->isFreeShipping($quote, $shippingAssignment->getItems()) - ); $total->setTotalAmount($this->getCode(), 0); $total->setBaseTotalAmount($this->getCode(), 0); @@ -98,7 +96,7 @@ class Shipping extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal $itemQty = $child->getTotalQty(); $rowWeight = $itemWeight * $itemQty; $addressWeight += $rowWeight; - if ($address->getFreeShipping() || $child->getFreeShipping() === true) { + if ($addressFreeShipping || $child->getFreeShipping() === true) { $rowWeight = 0; } elseif (is_numeric($child->getFreeShipping())) { $freeQty = $child->getFreeShipping(); @@ -116,7 +114,7 @@ class Shipping extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal $itemWeight = $item->getWeight(); $rowWeight = $itemWeight * $item->getQty(); $addressWeight += $rowWeight; - if ($address->getFreeShipping() || $item->getFreeShipping() === true) { + if ($addressFreeShipping || $item->getFreeShipping() === true) { $rowWeight = 0; } elseif (is_numeric($item->getFreeShipping())) { $freeQty = $item->getFreeShipping(); @@ -136,7 +134,7 @@ class Shipping extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal $itemWeight = $item->getWeight(); $rowWeight = $itemWeight * $item->getQty(); $addressWeight += $rowWeight; - if ($address->getFreeShipping() || $item->getFreeShipping() === true) { + if ($addressFreeShipping || $item->getFreeShipping() === true) { $rowWeight = 0; } elseif (is_numeric($item->getFreeShipping())) { $freeQty = $item->getFreeShipping(); @@ -157,6 +155,10 @@ class Shipping extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal $address->setWeight($addressWeight); $address->setFreeMethodWeight($freeMethodWeight); + $address->setFreeShipping( + $this->freeShipping->isFreeShipping($quote, $shippingAssignment->getItems()) + ); + $address->collectShippingRates(); if ($method) { diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote.php b/app/code/Magento/Quote/Model/ResourceModel/Quote.php index 241f0d6b272fc33f6b29ff4e2c069ca088c5073b..6113a1ba56ac184d59a6d0e37bd696455dce31cd 100644 --- a/app/code/Magento/Quote/Model/ResourceModel/Quote.php +++ b/app/code/Magento/Quote/Model/ResourceModel/Quote.php @@ -172,6 +172,29 @@ class Quote extends AbstractDb ->getNextValue(); } + /** + * Check if order increment ID is already used. + * Method can be used to avoid collisions of order IDs. + * + * @param int $orderIncrementId + * @return bool + */ + public function isOrderIncrementIdUsed($orderIncrementId) + { + /** @var \Magento\Framework\DB\Adapter\AdapterInterface $adapter */ + $adapter = $this->getConnection(); + $bind = [':increment_id' => $orderIncrementId]; + /** @var \Magento\Framework\DB\Select $select */ + $select = $adapter->select(); + $select->from($this->getTable('sales_order'), 'entity_id')->where('increment_id = :increment_id'); + $entity_id = $adapter->fetchOne($select, $bind); + if ($entity_id > 0) { + return true; + } + + return false; + } + /** * Mark quotes - that depend on catalog price rules - to be recollected on demand * diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php index 5165f299d45a96b7aa9b130a03747fad38955084..b54ce84fe1ac45fbee2d1b2c89a99c8546a11cab 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteTest.php @@ -314,7 +314,10 @@ class QuoteTest extends \PHPUnit_Framework_TestCase 'customerRepository' => $this->customerRepositoryMock, 'objectCopyService' => $this->objectCopyServiceMock, 'extensionAttributesJoinProcessor' => $this->extensionAttributesJoinProcessorMock, - 'customerDataFactory' => $this->customerDataFactoryMock + 'customerDataFactory' => $this->customerDataFactoryMock, + 'data' => [ + 'reserved_order_id' => 1000001 + ] ] ); } @@ -1237,4 +1240,30 @@ class QuoteTest extends \PHPUnit_Framework_TestCase $this->assertEquals($itemResult, $this->quote->getAllItems()); } + + /** + * Test to verify if existing reserved_order_id in use + * + * @param bool $isReservedOrderIdExist + * @param int $reservedOrderId + * @dataProvider reservedOrderIdDataProvider + */ + public function testReserveOrderId($isReservedOrderIdExist, $reservedOrderId) + { + $this->resourceMock + ->expects($this->once()) + ->method('isOrderIncrementIdUsed') + ->with(1000001)->willReturn($isReservedOrderIdExist); + $this->resourceMock->expects($this->any())->method('getReservedOrderId')->willReturn($reservedOrderId); + $this->quote->reserveOrderId(); + $this->assertEquals($reservedOrderId, $this->quote->getReservedOrderId()); + } + + public function reservedOrderIdDataProvider() + { + return [ + 'id_already_in_use' => [true, 100002], + 'id_not_in_use' => [false, 1000001] + ]; + } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php b/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..15c58073c2e26a4921f771e6c4097fafc9275c91 --- /dev/null +++ b/app/code/Magento/Quote/Test/Unit/Model/ResourceModel/QuoteTest.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Quote\Test\Unit\Model\ResourceModel; + +class QuoteTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Quote\Model\ResourceModel\Quote + */ + private $model; + + /** + * @var \Magento\Framework\App\ResourceConnection + */ + private $resourceMock; + + /** + * @var \Magento\Framework\DB\Adapter\Pdo\Mysql + */ + private $adapterMock; + + /** + * @var \Magento\Framework\DB\Select + */ + private $selectMock; + + protected function setUp() + { + $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class) + ->disableOriginalConstructor() + ->getMock(); + $this->selectMock->expects($this->any())->method('from')->will($this->returnSelf()); + $this->selectMock->expects($this->any())->method('where'); + + $this->adapterMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\Pdo\Mysql::class) + ->disableOriginalConstructor() + ->getMock(); + $this->adapterMock->expects($this->any())->method('select')->will($this->returnValue($this->selectMock)); + + $this->resourceMock = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class) + ->disableOriginalConstructor() + ->getMock(); + $this->resourceMock->expects( + $this->any() + )->method( + 'getConnection' + )->will( + $this->returnValue($this->adapterMock) + ); + + $this->model = $objectManagerHelper->getObject( + \Magento\Quote\Model\ResourceModel\Quote::class, + [ + 'resource' => $this->resourceMock + ] + ); + } + + /** + * Unit test to verify if isOrderIncrementIdUsed method works with different types increment ids + * + * @param array $value + * @dataProvider isOrderIncrementIdUsedDataProvider + */ + public function testIsOrderIncrementIdUsed($value) + { + $expectedBind = [':increment_id' => $value]; + $this->adapterMock->expects($this->once())->method('fetchOne')->with($this->selectMock, $expectedBind); + $this->model->isOrderIncrementIdUsed($value); + } + + /** + * @return array + */ + public function isOrderIncrementIdUsedDataProvider() + { + return [[100000001], ['10000000001'], ['M10000000001']]; + } +} diff --git a/app/code/Magento/Quote/composer.json b/app/code/Magento/Quote/composer.json index 79c3a1aaa9d907cea1c505ece12a22e4ee3986f2..76063b177d46c2eb93626dea77a346aaaeb5c21d 100644 --- a/app/code/Magento/Quote/composer.json +++ b/app/code/Magento/Quote/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-quote", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Reports/composer.json b/app/code/Magento/Reports/composer.json index 0bb989cb54ac8cb84e3da943a231634b6707f256..9c76abd07b9b35276f1f923138c9dd47031c289d 100644 --- a/app/code/Magento/Reports/composer.json +++ b/app/code/Magento/Reports/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-reports", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/RequireJs/composer.json b/app/code/Magento/RequireJs/composer.json index 8c9fed33ab730dd2431b222b2edd04aa2252e12f..68428caa867bf7108fc466ebfd1fa2829a2b75fa 100644 --- a/app/code/Magento/RequireJs/composer.json +++ b/app/code/Magento/RequireJs/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-require-js", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*" }, "type": "magento2-module", diff --git a/app/code/Magento/Review/composer.json b/app/code/Magento/Review/composer.json index 09f5529feabdd97512839be2dacd00f791083c3c..3ec82943476088c3aa307de1b5a934a3cac8ca0d 100644 --- a/app/code/Magento/Review/composer.json +++ b/app/code/Magento/Review/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-review", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Review/view/frontend/layout/customer_account.xml b/app/code/Magento/Review/view/frontend/layout/customer_account.xml index 9de10e43fa4d148dce5c4b316e93538a830e6d2b..5506a904ce0f76bce2a8552d517a0f31646644fb 100644 --- a/app/code/Magento/Review/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Review/view/frontend/layout/customer_account.xml @@ -8,10 +8,11 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-product-reviews-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-product-reviews-link"> <arguments> <argument name="path" xsi:type="string">review/customer</argument> <argument name="label" xsi:type="string">My Product Reviews</argument> + <argument name="sortOrder" xsi:type="number">50</argument> </arguments> </block> </referenceBlock> diff --git a/app/code/Magento/Review/view/frontend/templates/form.phtml b/app/code/Magento/Review/view/frontend/templates/form.phtml index 7c89fd8740c5607ec83f0d0a4dda761de50d97ed..bcac1dbca059833749faf9db4d759f8a06a6f24a 100644 --- a/app/code/Magento/Review/view/frontend/templates/form.phtml +++ b/app/code/Magento/Review/view/frontend/templates/form.phtml @@ -20,7 +20,7 @@ <?php if ($block->getRatings() && $block->getRatings()->getSize()): ?> <span id="input-message-box"></span> <fieldset class="field required review-field-ratings"> - <legend class="label"><span><?php echo $block->escapeHtml(__('Your Rating')) ?><span></legend><br/> + <legend class="label"><span><?php echo $block->escapeHtml(__('Your Rating')) ?></span></legend><br/> <div class="control"> <div class="nested" id="product-review-table"> <?php foreach ($block->getRatings() as $_rating): ?> diff --git a/app/code/Magento/Rss/composer.json b/app/code/Magento/Rss/composer.json index d35f0ee6ebecbf75af87566d776f172ab3d22c0d..fedd0ffeaacfe9577ea6e5971411540d4925fe4c 100644 --- a/app/code/Magento/Rss/composer.json +++ b/app/code/Magento/Rss/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-rss", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/framework": "100.2.*", diff --git a/app/code/Magento/Rule/composer.json b/app/code/Magento/Rule/composer.json index 95b8e6ffec2a682895b3459ca7bdcc7b4f001669..31ee3254d99b98a10ce9973e3a8a4adb1c5818da 100644 --- a/app/code/Magento/Rule/composer.json +++ b/app/code/Magento/Rule/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-rule", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-eav": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/Sales/Model/Order/Payment.php b/app/code/Magento/Sales/Model/Order/Payment.php index 5deb24a068de1d0ff8a48bb4cc7f6f916c1c6dde..a0f56d6ea9a73ea02d4c852ef6ac4831be29b574 100644 --- a/app/code/Magento/Sales/Model/Order/Payment.php +++ b/app/code/Magento/Sales/Model/Order/Payment.php @@ -632,42 +632,48 @@ class Payment extends Info implements OrderPaymentInterface $this->transactionManager->generateTransactionId($this, Transaction::TYPE_REFUND) ); - // call refund from gateway if required $isOnline = false; $gateway = $this->getMethodInstance(); $invoice = null; - if ($gateway->canRefund() && $creditmemo->getDoTransaction()) { + if ($gateway->canRefund()) { $this->setCreditmemo($creditmemo); - $invoice = $creditmemo->getInvoice(); - if ($invoice) { - $isOnline = true; - $captureTxn = $this->transactionRepository->getByTransactionId( - $invoice->getTransactionId(), - $this->getId(), - $this->getOrder()->getId() - ); - if ($captureTxn) { - $this->setTransactionIdsForRefund($captureTxn); - } - $this->setShouldCloseParentTransaction(true); - // TODO: implement multiple refunds per capture - try { - $gateway->setStore( - $this->getOrder()->getStoreId() + if ($creditmemo->getDoTransaction()) { + $invoice = $creditmemo->getInvoice(); + if ($invoice) { + $isOnline = true; + $captureTxn = $this->transactionRepository->getByTransactionId( + $invoice->getTransactionId(), + $this->getId(), + $this->getOrder()->getId() ); - $this->setRefundTransactionId($invoice->getTransactionId()); - $gateway->refund($this, $baseAmountToRefund); - - $creditmemo->setTransactionId($this->getLastTransId()); - } catch (\Magento\Framework\Exception\LocalizedException $e) { - if (!$captureTxn) { - throw new \Magento\Framework\Exception\LocalizedException( - __('If the invoice was created offline, try creating an offline credit memo.'), - $e + if ($captureTxn) { + $this->setTransactionIdsForRefund($captureTxn); + } + $this->setShouldCloseParentTransaction(true); + // TODO: implement multiple refunds per capture + try { + $gateway->setStore( + $this->getOrder()->getStoreId() ); + $this->setRefundTransactionId($invoice->getTransactionId()); + $gateway->refund($this, $baseAmountToRefund); + + $creditmemo->setTransactionId($this->getLastTransId()); + } catch (\Magento\Framework\Exception\LocalizedException $e) { + if (!$captureTxn) { + throw new \Magento\Framework\Exception\LocalizedException( + __('If the invoice was created offline, try creating an offline credit memo.'), + $e + ); + } + throw $e; } - throw $e; } + } else if ($gateway->isOffline()) { + $gateway->setStore( + $this->getOrder()->getStoreId() + ); + $gateway->refund($this, $baseAmountToRefund); } } diff --git a/app/code/Magento/Sales/Setup/UpgradeSchema.php b/app/code/Magento/Sales/Setup/UpgradeSchema.php index b977cb28596eae3ff2e9c5ea0836ba716c54831e..d35825242fb291a41961eb882ad93f7d24c58ee9 100644 --- a/app/code/Magento/Sales/Setup/UpgradeSchema.php +++ b/app/code/Magento/Sales/Setup/UpgradeSchema.php @@ -69,6 +69,20 @@ class UpgradeSchema implements UpgradeSchemaInterface $this->addColumnBaseGrandTotal($installer); $this->addIndexBaseGrandTotal($installer); } + if (version_compare($context->getVersion(), '2.0.4', '<')) { + $tables = [ + 'sales_invoice_grid', + 'sales_order', + 'sales_shipment_grid', + ]; + foreach ($tables as $table) { + $setup->getConnection()->modifyColumn( + $setup->getTable($table), + 'customer_group_id', + ['type' => 'integer'] + ); + } + } } /** diff --git a/app/code/Magento/Sales/composer.json b/app/code/Magento/Sales/composer.json index 09d1a9d6b7dd41ea95a09b26c0f72276327fe245..6b5638455a1c5986fb487f7f9c948d9ddf19aad3 100644 --- a/app/code/Magento/Sales/composer.json +++ b/app/code/Magento/Sales/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sales", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Sales/etc/module.xml b/app/code/Magento/Sales/etc/module.xml index 88d6e25d31fb6c4ed9777595296e969028b154f2..980395e965e0899695206b7094b72f7f5b396ece 100644 --- a/app/code/Magento/Sales/etc/module.xml +++ b/app/code/Magento/Sales/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_Sales" setup_version="2.0.3"> + <module name="Magento_Sales" setup_version="2.0.4"> <sequence> <module name="Magento_Rule"/> <module name="Magento_Catalog"/> diff --git a/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml index 775c7214c2f104771c79d5fd299229187595b167..20cfcdeadf22a0f18d81b262134058640c04877f 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/order/view/info.phtml @@ -38,10 +38,10 @@ $orderStoreDate = $block->formatDate( <div class="admin__page-section-item-title"> <span class="title"> <?php if ($block->getNoUseOrderLink()): ?> - <?php /* @escapeNotVerified */ echo __('Order # %1', $_order->getRealOrderId()) ?> (<span><?php /* @escapeNotVerified */ echo $_email ?></span>) + <?php /* @escapeNotVerified */ echo __('Order # %1', $_order->getRealOrderId()) ?> (<span><?php echo $block->escapeHtml($_email) ?></span>) <?php else: ?> <a href="<?php /* @escapeNotVerified */ echo $block->getViewUrl($_order->getId()) ?>"><?php /* @escapeNotVerified */ echo __('Order # %1', $_order->getRealOrderId()) ?></a> - <span>(<?php /* @escapeNotVerified */ echo $_email ?>)</span> + <span>(<?php echo $block->escapeHtml($_email) ?>)</span> <?php endif; ?> </span> </div> @@ -49,7 +49,7 @@ $orderStoreDate = $block->formatDate( <table class="admin__table-secondary order-information-table"> <tr> <th><?php /* @escapeNotVerified */ echo __('Order Date') ?></th> - <td><?php /* @escapeNotVerified */ echo $orderAdminDate ?></td> + <td><?php echo $block->escapeHtml($orderAdminDate) ?></td> </tr> <?php if ($orderAdminDate != $orderStoreDate):?> <tr> @@ -57,12 +57,12 @@ $orderStoreDate = $block->formatDate( 'Order Date (%1)', $block->getTimezoneForStore($_order->getStore()) ) ?></th> - <td><?php /* @escapeNotVerified */ echo $orderStoreDate ?></td> + <td><?php echo $block->escapeHtml($orderStoreDate) ?></td> </tr> <?php endif;?> <tr> <th><?php /* @escapeNotVerified */ echo __('Order Status') ?></th> - <td><span id="order_status"><?php /* @escapeNotVerified */ echo $_order->getStatusLabel() ?></span></td> + <td><span id="order_status"><?php echo $block->escapeHtml($_order->getStatusLabel()) ?></span></td> </tr> <?php echo $block->getChildHtml(); ?> <?php if ($block->isSingleStoreMode() == false):?> @@ -136,13 +136,13 @@ $orderStoreDate = $block->formatDate( <?php if ($_groupName = $block->getCustomerGroupName()) : ?> <tr> <th><?php /* @escapeNotVerified */ echo __('Customer Group') ?></th> - <td><?php /* @escapeNotVerified */ echo $_groupName ?></td> + <td><?php echo $block->escapeHtml($_groupName) ?></td> </tr> <?php endif; ?> <?php foreach ($block->getCustomerAccountData() as $data):?> <tr> - <th><?php /* @escapeNotVerified */ echo $data['label'] ?></th> - <td><?php /* @escapeNotVerified */ echo $data['value'] ?></td> + <th><?php echo $block->escapeHtml($data['label']) ?></th> + <td><?php echo $block->escapeHtml($data['value']) ?></td> </tr> <?php endforeach;?> </table> diff --git a/app/code/Magento/Sales/view/frontend/layout/customer_account.xml b/app/code/Magento/Sales/view/frontend/layout/customer_account.xml index a3933b8b221109b895f79646f6e0a9d36dc90905..239a38c09f5058c4f30fa86eb9bd0858517ad8b9 100644 --- a/app/code/Magento/Sales/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Sales/view/frontend/layout/customer_account.xml @@ -8,10 +8,11 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-orders-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-orders-link"> <arguments> <argument name="path" xsi:type="string">sales/order/history</argument> <argument name="label" xsi:type="string" translate="true">My Orders</argument> + <argument name="sortOrder" xsi:type="number">230</argument> </arguments> </block> </referenceBlock> diff --git a/app/code/Magento/SalesInventory/composer.json b/app/code/Magento/SalesInventory/composer.json index ff72ce7f0022690682b356235c506512e65497b9..d7f9075cdd310141bab6b25869f33d6229f513dc 100644 --- a/app/code/Magento/SalesInventory/composer.json +++ b/app/code/Magento/SalesInventory/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sales-inventory", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog-inventory": "100.2.*", "magento/module-sales": "100.2.*", "magento/module-store": "100.2.*", diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php index 57477b5ce0623f57bfc048839cc8bdaee8d40593..efa45512acc8320cd878c97b92c5912c7713f882 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php @@ -26,8 +26,13 @@ class Save extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote ['request' => $this->getRequest()] ); $data = $this->getRequest()->getPostValue(); + + $filterValues = ['from_date' => $this->_dateFilter]; + if ($this->getRequest()->getParam('to_date')) { + $filterValues['to_date'] = $this->_dateFilter; + } $inputFilter = new \Zend_Filter_Input( - ['from_date' => $this->_dateFilter, 'to_date' => $this->_dateFilter], + $filterValues, [], $data ); diff --git a/app/code/Magento/SalesRule/Setup/InstallSchema.php b/app/code/Magento/SalesRule/Setup/InstallSchema.php index 577d243cd6e1c0848f78328d26dfbf979216791c..9adf43fc19cce981bb9915bfe614a242cbce013d 100644 --- a/app/code/Magento/SalesRule/Setup/InstallSchema.php +++ b/app/code/Magento/SalesRule/Setup/InstallSchema.php @@ -23,7 +23,9 @@ class InstallSchema implements InstallSchemaInterface { $installer = $setup; $installer->startSetup(); - + $customerGroupTable = $setup->getConnection()->describeTable($setup->getTable('customer_group')); + $customerGroupIdType = $customerGroupTable['customer_group_id']['DATA_TYPE'] == 'int' + ? \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER : $customerGroupTable['customer_group_id']['DATA_TYPE']; /** * Create table 'salesrule' */ @@ -441,7 +443,7 @@ class InstallSchema implements InstallSchemaInterface 'Website Id' )->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Customer Group Id' @@ -757,7 +759,7 @@ class InstallSchema implements InstallSchemaInterface 'Rule Id' )->addColumn( 'customer_group_id', - \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, + $customerGroupIdType, null, ['unsigned' => true, 'nullable' => false, 'primary' => true], 'Customer Group Id' diff --git a/app/code/Magento/SalesRule/composer.json b/app/code/Magento/SalesRule/composer.json index fabef581762a84cac8844bf7e7099b0bb8b6b6c0..8479acad47246a1f91bc6b098d88cfa6f6027f76 100644 --- a/app/code/Magento/SalesRule/composer.json +++ b/app/code/Magento/SalesRule/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sales-rule", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-rule": "100.2.*", diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/action/cancel-coupon.js b/app/code/Magento/SalesRule/view/frontend/web/js/action/cancel-coupon.js index ed5744feb6ce84df31a5a59b42f520bb97316792..d1e294c9a8f0c1dc4edf82e52cbe509b09da8fbe 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/action/cancel-coupon.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/action/cancel-coupon.js @@ -17,16 +17,20 @@ define( 'mage/storage', 'Magento_Checkout/js/action/get-payment-information', 'Magento_Checkout/js/model/totals', - 'mage/translate' + 'mage/translate', + 'Magento_Checkout/js/model/full-screen-loader' ], - function ($, quote, urlManager, errorProcessor, messageContainer, storage, getPaymentInformationAction, totals, $t) { + function ($, quote, urlManager, errorProcessor, messageContainer, storage, getPaymentInformationAction, totals, $t, + fullScreenLoader) { 'use strict'; - return function (isApplied, isLoading) { + return function (isApplied) { var quoteId = quote.getQuoteId(), url = urlManager.getCancelCouponUrl(quoteId), message = $t('Your coupon was successfully removed.'); + messageContainer.clear(); + fullScreenLoader.startLoader(); return storage.delete( url, @@ -39,6 +43,7 @@ define( $.when(deferred).done(function () { isApplied(false); totals.isLoading(false); + fullScreenLoader.stopLoader(); }); messageContainer.addSuccessMessage({ 'message': message @@ -47,12 +52,9 @@ define( ).fail( function (response) { totals.isLoading(false); + fullScreenLoader.stopLoader(); errorProcessor.process(response, messageContainer); } - ).always( - function () { - isLoading(false); - } ); }; } diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js b/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js index 606fe4013ea762c5460c49202907463d7c7952f2..a2b7ff19f2139ffa7e23b37eda039d5b9ccc32a8 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/action/set-coupon-code.js @@ -18,25 +18,22 @@ define( 'mage/storage', 'mage/translate', 'Magento_Checkout/js/action/get-payment-information', - 'Magento_Checkout/js/model/totals' + 'Magento_Checkout/js/model/totals', + 'Magento_Checkout/js/model/full-screen-loader' ], function ( - ko, - $, - quote, - urlManager, - errorProcessor, - messageContainer, - storage, - $t, - getPaymentInformationAction, - totals + ko, $, quote, urlManager, errorProcessor, messageContainer, storage, $t, getPaymentInformationAction, totals, + fullScreenLoader ) { 'use strict'; - return function (couponCode, isApplied, isLoading) { - var quoteId = quote.getQuoteId(); - var url = urlManager.getApplyCouponUrl(couponCode, quoteId); - var message = $t('Your coupon was successfully applied.'); + + return function (couponCode, isApplied) { + var quoteId = quote.getQuoteId(), + url = urlManager.getApplyCouponUrl(couponCode, quoteId), + message = $t('Your coupon was successfully applied.'); + + fullScreenLoader.startLoader(); + return storage.put( url, {}, @@ -45,19 +42,22 @@ define( function (response) { if (response) { var deferred = $.Deferred(); - isLoading(false); + isApplied(true); totals.isLoading(true); getPaymentInformationAction(deferred); $.when(deferred).done(function () { + fullScreenLoader.stopLoader(); totals.isLoading(false); }); - messageContainer.addSuccessMessage({'message': message}); + messageContainer.addSuccessMessage({ + 'message': message + }); } } ).fail( function (response) { - isLoading(false); + fullScreenLoader.stopLoader(); totals.isLoading(false); errorProcessor.process(response, messageContainer); } diff --git a/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js b/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js index 8e8c0798ff3b59ce0ffa0a803242abe1d02fcee7..6dc973a7a1e4298469251f71f6e26f09a95cc5d0 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js +++ b/app/code/Magento/SalesRule/view/frontend/web/js/view/payment/discount.js @@ -13,49 +13,53 @@ define( ], function ($, ko, Component, quote, setCouponCodeAction, cancelCouponAction) { 'use strict'; - var totals = quote.getTotals(); - var couponCode = ko.observable(null); + + var totals = quote.getTotals(), + couponCode = ko.observable(null), + isApplied = ko.observable(couponCode() != null); + if (totals()) { couponCode(totals()['coupon_code']); } - var isApplied = ko.observable(couponCode() != null); - var isLoading = ko.observable(false); + return Component.extend({ defaults: { template: 'Magento_SalesRule/payment/discount' }, couponCode: couponCode, + /** * Applied flag */ isApplied: isApplied, - isLoading: isLoading, + /** * Coupon code application procedure */ apply: function() { if (this.validate()) { - isLoading(true); - setCouponCodeAction(couponCode(), isApplied, isLoading); + setCouponCodeAction(couponCode(), isApplied); } }, + /** * Cancel using coupon */ cancel: function() { if (this.validate()) { - isLoading(true); couponCode(''); - cancelCouponAction(isApplied, isLoading); + cancelCouponAction(isApplied); } }, + /** * Coupon form validation * - * @returns {boolean} + * @returns {Boolean} */ - validate: function() { + validate: function () { var form = '#discount-form'; + return $(form).validation() && $(form).validation('isValid'); } }); diff --git a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html index d6bc4c764d5714382d017adce856deaf84c8b3ff..4ba38906fe9c1e98c8d248574585cc01ef815914 100644 --- a/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html +++ b/app/code/Magento/SalesRule/view/frontend/web/template/payment/discount.html @@ -15,7 +15,7 @@ <!-- ko foreach: getRegion('messages') --> <!-- ko template: getTemplate() --><!-- /ko --> <!--/ko--> - <form class="form form-discount" id="discount-form" data-bind="blockLoader: isLoading"> + <form class="form form-discount" id="discount-form"> <div class="payment-option-inner"> <div class="field"> <label class="label" for="discount-code"> diff --git a/app/code/Magento/SalesSequence/composer.json b/app/code/Magento/SalesSequence/composer.json index eeda2be05e68f86cb5552e6f48ca502dd96cb9be..5b9efe61f72724e28d23c1af0dc197fede8e117f 100644 --- a/app/code/Magento/SalesSequence/composer.json +++ b/app/code/Magento/SalesSequence/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sales-sequence", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*" }, "type": "magento2-module", diff --git a/app/code/Magento/SampleData/composer.json b/app/code/Magento/SampleData/composer.json index 936bbf0d6be3a7399babc2e835282e7c71bea4c2..4c98a5abd3a16ab72b5ea04e0606955986924ebd 100644 --- a/app/code/Magento/SampleData/composer.json +++ b/app/code/Magento/SampleData/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sample-data", "description": "Sample Data fixtures", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*" }, "suggest": { diff --git a/app/code/Magento/Search/composer.json b/app/code/Magento/Search/composer.json index cc95e6784526f33bb65246b599a4147e8a1afa1d..8f809fb3b8af3533c4715aea1e13854650e0cbac 100644 --- a/app/code/Magento/Search/composer.json +++ b/app/code/Magento/Search/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-search", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-catalog-search": "100.2.*", diff --git a/app/code/Magento/Security/composer.json b/app/code/Magento/Security/composer.json index f64c0f0b29d282b0feaefb24ebb3963086f0e083..4048b63bf607030d27ae7fa84dbec6a0da838957 100644 --- a/app/code/Magento/Security/composer.json +++ b/app/code/Magento/Security/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-security", "description": "Security management module", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/module-store": "100.2.*", "magento/framework": "100.2.*" diff --git a/app/code/Magento/SendFriend/composer.json b/app/code/Magento/SendFriend/composer.json index de9dbf7a84576022d8f6b1a38324f902565870df..1095dedc0ba6606107660d65373096d8c82b204c 100644 --- a/app/code/Magento/SendFriend/composer.json +++ b/app/code/Magento/SendFriend/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-send-friend", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-customer": "100.2.*", diff --git a/app/code/Magento/Shipping/composer.json b/app/code/Magento/Shipping/composer.json index dc9ea808c0f7b19e396a7c73f2b416221729a34b..03f51bbd95e0ff5f65b88e565cfbe77102a44760 100644 --- a/app/code/Magento/Shipping/composer.json +++ b/app/code/Magento/Shipping/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-shipping", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-sales": "100.2.*", diff --git a/app/code/Magento/Sitemap/composer.json b/app/code/Magento/Sitemap/composer.json index be553a4da194f4dbf64fb4237fd92bb27e4e17ff..9f556178fc2cccfd070c272a89164e438f1d33e8 100644 --- a/app/code/Magento/Sitemap/composer.json +++ b/app/code/Magento/Sitemap/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sitemap", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/Store/composer.json b/app/code/Magento/Store/composer.json index b9f76f6c2c3137aa21d8f4a6cf97880fdc3e72f6..d5b8a2b4b980b58648c04cfed916b5bd390db048 100644 --- a/app/code/Magento/Store/composer.json +++ b/app/code/Magento/Store/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-store", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-directory": "100.2.*", "magento/module-ui": "100.2.*", diff --git a/app/code/Magento/Swagger/composer.json b/app/code/Magento/Swagger/composer.json index dc2d1cbee003de647cf72f7312ad7f7fcc222e3b..47e347426c83fdb1b969c5a03bd46d1776e89950 100644 --- a/app/code/Magento/Swagger/composer.json +++ b/app/code/Magento/Swagger/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-swagger", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*" }, "type": "magento2-module", diff --git a/app/code/Magento/Swatches/composer.json b/app/code/Magento/Swatches/composer.json index f87fdb635cb73386b40fec6ac932eaee3f9cd398..b61ff41d8c955890aae0c22baedcdb8abd22e004 100644 --- a/app/code/Magento/Swatches/composer.json +++ b/app/code/Magento/Swatches/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-swatches", "description": "Add Swatches to Products", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-configurable-product": "100.2.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/SwatchesLayeredNavigation/composer.json b/app/code/Magento/SwatchesLayeredNavigation/composer.json index 9b658d0a42e61e0b556f1aa5aed6e12ed8cfdbe7..67c6d90c51e7f81de645451da6553bcdb750fddd 100644 --- a/app/code/Magento/SwatchesLayeredNavigation/composer.json +++ b/app/code/Magento/SwatchesLayeredNavigation/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-swatches-layered-navigation", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*", "magento/magento-composer-installer": "*" }, diff --git a/app/code/Magento/Tax/composer.json b/app/code/Magento/Tax/composer.json index 4553fdbb383323732b667df9be3e140aca011744..3dfacaaecd9635dec8229175de2876da6c957a77 100644 --- a/app/code/Magento/Tax/composer.json +++ b/app/code/Magento/Tax/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-tax", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-config": "100.2.*", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/TaxImportExport/composer.json b/app/code/Magento/TaxImportExport/composer.json index c3d09e4926e02d67a98c0e15c050d67997ae1ecd..9d9cac5dec59c94e8013bc5ab3275fee84c73b6b 100644 --- a/app/code/Magento/TaxImportExport/composer.json +++ b/app/code/Magento/TaxImportExport/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-tax-import-export", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-tax": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-directory": "100.2.*", diff --git a/app/code/Magento/Theme/composer.json b/app/code/Magento/Theme/composer.json index 49382f9642fa5d2b88d7d2f4cea79c07d5306cb1..7158ac314076d38aae4766bbea1a917dc869b5dd 100644 --- a/app/code/Magento/Theme/composer.json +++ b/app/code/Magento/Theme/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-theme", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-customer": "100.2.*", "magento/module-backend": "100.2.*", diff --git a/app/code/Magento/Theme/view/frontend/layout/default.xml b/app/code/Magento/Theme/view/frontend/layout/default.xml index a2e89afcce1ee0461921678bb1c5888c7dd3151f..0f020db7501ae725b25f0bd8bfda51ae5c74be1c 100644 --- a/app/code/Magento/Theme/view/frontend/layout/default.xml +++ b/app/code/Magento/Theme/view/frontend/layout/default.xml @@ -40,7 +40,7 @@ </arguments> </block> <block class="Magento\Store\Block\Switcher" name="store_language" as="store_language" template="switch/languages.phtml"/> - <block class="Magento\Framework\View\Element\Html\Links" name="top.links"> + <block class="Magento\Customer\Block\Account\Navigation" name="top.links"> <arguments> <argument name="css_class" xsi:type="string">header links</argument> </arguments> diff --git a/app/code/Magento/Translation/composer.json b/app/code/Magento/Translation/composer.json index 9ea00544d805094927499672f98fb72ad12ae97a..b9ed830c9053ea492d76a2402ec8f523fb03a46a 100644 --- a/app/code/Magento/Translation/composer.json +++ b/app/code/Magento/Translation/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-translation", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/module-developer": "100.2.*", "magento/module-store": "100.2.*", diff --git a/app/code/Magento/Ui/README.md b/app/code/Magento/Ui/README.md index 1337383ea3fb83bf3e2927589859587aad5a48f7..b7dd1a858e4a8161f836b3ea73d1483ae56ba2dd 100644 --- a/app/code/Magento/Ui/README.md +++ b/app/code/Magento/Ui/README.md @@ -1,7 +1,7 @@ # Overview ## Purpose of module -The Magento\Ui module introduces a set of common UI components, which could be easily used and configured via layout XML files. +The Magento\Ui module introduces a set of common UI components, which could be used and configured via layout XML files. # Deployment ## System requirements diff --git a/app/code/Magento/Ui/composer.json b/app/code/Magento/Ui/composer.json index 43c7b7fa3011fd7c6386fcde17eb45b6c5e02695..a3de6c6dfbdde7860dbb07503e0e6fe25e8b3dcf 100644 --- a/app/code/Magento/Ui/composer.json +++ b/app/code/Magento/Ui/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-ui", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/framework": "100.2.*", "magento/module-eav": "100.2.*", diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js index de10c065d123bf773f9fba66135b2308a2abe3e9..518f09fa73ba6c6771654c7c9fc3f3ce07be79be 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows-grid.js @@ -52,13 +52,15 @@ define([ obj; if (this.recordData().length && !this.update) { - this.recordData.each(function (recordData) { + _.each(this.recordData(), function (recordData) { obj = {}; obj[this.map[this.identificationProperty]] = recordData[this.identificationProperty]; insertData.push(obj); }, this); - this.source.set(this.dataProvider, insertData); + if (insertData.length) { + this.source.set(this.dataProvider, insertData); + } } }, @@ -178,7 +180,7 @@ define([ tmpObj = {}; if (data.length !== this.relatedData.length) { - data.forEach(function (obj) { + _.each(data, function (obj) { tmpObj[this.identificationDRProperty] = obj[this.identificationDRProperty]; if (!_.findWhere(this.relatedData, tmpObj)) { @@ -193,7 +195,7 @@ define([ /** * Processing insert data * - * @param {Array} data + * @param {Object} data */ processingInsertData: function (data) { var changes, diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js index 1805dcd2f004ec91e5d3d1a96d2b0086e4b69e8c..917dc62f9f49b148d58ad62c671eb6fbeeff8beb 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js @@ -294,15 +294,20 @@ define([ /** * Handler which is invoked prior to the start of a file upload. * - * @param {Event} e - Event obejct. + * @param {Event} e - Event object. * @param {Object} data - File data that will be uploaded. */ onBeforeFileUpload: function (e, data) { var file = data.files[0], - allowed = this.isFileAllowed(file); + allowed = this.isFileAllowed(file), + target = $(e.target); if (allowed.passed) { - $(e.target).fileupload('process', data).done(function () { + target.on('fileuploadsend', function (event, postData) { + postData.data.set('param_name', this.paramName); + }.bind(data)); + + target.fileupload('process', data).done(function () { data.submit(); }); } else { diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js b/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js index be312c71f1fb20a1a2140765d598d8567bfaf648..b640b131aaf6fcb52ca883da80de65ab0cd75dd3 100644 --- a/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js +++ b/app/code/Magento/Ui/view/base/web/js/form/element/ui-select.js @@ -161,7 +161,6 @@ define([ defaultPlaceholder: $t('Select...'), lotPlaceholders: $t('Selected') }, - hoverElIndex: null, separator: 'optgroup', listens: { listVisible: 'cleanHoveredElement', @@ -295,7 +294,6 @@ define([ this._super(); this.observe([ 'listVisible', - 'hoverElIndex', 'placeholder', 'multiselectFocus', 'options', @@ -539,7 +537,7 @@ define([ }, /** - * Clean hoverElIndex variable + * Clean hoveredElement variable * * @returns {Object} Chainable */ diff --git a/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js b/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js index 49133721369c85dba954788626875d1a13c26a1e..493b8c78f0cdfbf56f43c8775ab2fcd0edf5ff64 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/core/collection.js @@ -268,6 +268,14 @@ define([ _.each(grouped, this.updateRegion, this); + _.each(this.regions, function (items) { + var hasObsoleteComponents = items().length && !_.intersection(_elems, items()).length; + + if (hasObsoleteComponents) { + items.removeAll(); + } + }); + this.elems(_elems); return this; diff --git a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/datepicker.js b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/datepicker.js index 16c102b8367f4b8762030057c4d249adb132693b..77d3a069ccef76bbcacad28459820b3359b025e7 100644 --- a/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/datepicker.js +++ b/app/code/Magento/Ui/view/base/web/js/lib/knockout/bindings/datepicker.js @@ -51,7 +51,12 @@ define([ observable() && $(el).datepicker( 'setDate', - moment(observable(), utils.normalizeDate(config.options.dateFormat)).toDate() + moment( + observable(), + utils.normalizeDate( + options.dateFormat + (options.showsTime ? ' ' + options.timeFormat : '') + ) + ).toDate() ); $(el).blur(); diff --git a/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/collapsible.html b/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/collapsible.html index f3319a05525f2f55bc4ba43ec5ce9bc1e4e2c300..d1ec1d26df6c56dff099b09aa0eb83232687573e 100644 --- a/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/collapsible.html +++ b/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/collapsible.html @@ -43,6 +43,7 @@ </div> <button class="action-delete" + data-index="delete_button" type="button" title="'Delete'" click="function(){ diff --git a/app/code/Magento/Ups/composer.json b/app/code/Magento/Ups/composer.json index ade4e738a68efe0499041830892bb6b61f5a3156..c20dd28ba88876a70c04ea49038b94f82d3d9b06 100644 --- a/app/code/Magento/Ups/composer.json +++ b/app/code/Magento/Ups/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-ups", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-backend": "100.2.*", "magento/module-sales": "100.2.*", diff --git a/app/code/Magento/UrlRewrite/composer.json b/app/code/Magento/UrlRewrite/composer.json index ac75ecdf61b8fd0531f2b14f76d2aec7d72581be..290ffc6731d89ce142e94c58616ca1a74c82c54b 100644 --- a/app/code/Magento/UrlRewrite/composer.json +++ b/app/code/Magento/UrlRewrite/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-url-rewrite", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-catalog": "101.1.*", "magento/module-store": "100.2.*", "magento/framework": "100.2.*", diff --git a/app/code/Magento/User/Block/User/Edit.php b/app/code/Magento/User/Block/User/Edit.php index f6c5b484861de36850d848e0eba2382d9688dade..116acd132a8487dbe56d933f307c40e62327bf6e 100644 --- a/app/code/Magento/User/Block/User/Edit.php +++ b/app/code/Magento/User/Block/User/Edit.php @@ -50,7 +50,7 @@ class Edit extends \Magento\Backend\Block\Widget\Form\Container $this->buttonList->update('save', 'label', __('Save User')); $this->buttonList->remove('delete'); - $objId = $this->getRequest()->getParam($this->_objectId); + $objId = (int)$this->getRequest()->getParam($this->_objectId); if (!empty($objId)) { $this->addButton( @@ -58,12 +58,9 @@ class Edit extends \Magento\Backend\Block\Widget\Form\Container [ 'label' => __('Delete User'), 'class' => 'delete', - 'onclick' => sprintf( - 'deleteConfirm("%s", "%s", %s)', - __('Are you sure you want to do this?'), - $this->getUrl('adminhtml/*/delete'), - json_encode(['data' => ['user_id' => $objId]]) - ), + 'data_attribute' => [ + 'role' => 'delete-user' + ] ] ); @@ -79,6 +76,44 @@ class Edit extends \Magento\Backend\Block\Widget\Form\Container } } + /** + * Returns message that is displayed for admin when he deletes user from the system. + * To see this message admin must do the following: + * - open user's account for editing; + * - type current user's password in the "Current User Identity Verification" field + * - click "Delete User" at top left part of the page; + * + * @return \Magento\Framework\Phrase + */ + public function getDeleteMessage() + { + return __('Are you sure you want to do this?'); + } + + /** + * Returns the URL that is used for user deletion. + * The following Action is executed if admin navigates to returned url + * Magento\User\Controller\Adminhtml\User\Delete + * + * @return string + */ + public function getDeleteUrl() + { + return $this->getUrl('adminhtml/*/delete'); + } + + /** + * This method is used to get the ID of the user who's account the Admin is editing. + * It can be used to determine the reason Admin opens the page: + * to create a new user account OR to edit the previously created user account + * + * @return int + */ + public function getObjectId() + { + return (int)$this->getRequest()->getParam($this->_objectId); + } + /** * @return \Magento\Framework\Phrase */ diff --git a/app/code/Magento/User/Controller/Adminhtml/User/Delete.php b/app/code/Magento/User/Controller/Adminhtml/User/Delete.php index d892c8533a29716b1206ba8ee8dbeaccfc3defce..3ff336eeecff87361e79f260f30f0b77cd2c1a03 100644 --- a/app/code/Magento/User/Controller/Adminhtml/User/Delete.php +++ b/app/code/Magento/User/Controller/Adminhtml/User/Delete.php @@ -6,6 +6,9 @@ */ namespace Magento\User\Controller\Adminhtml\User; +use Magento\User\Block\User\Edit\Tab\Main as UserEdit; +use Magento\Framework\Exception\AuthenticationException; + class Delete extends \Magento\User\Controller\Adminhtml\User { /** @@ -13,8 +16,10 @@ class Delete extends \Magento\User\Controller\Adminhtml\User */ public function execute() { + /** @var \Magento\User\Model\User */ $currentUser = $this->_objectManager->get(\Magento\Backend\Model\Auth\Session::class)->getUser(); $userId = (int)$this->getRequest()->getPost('user_id'); + if ($userId) { if ($currentUser->getId() == $userId) { $this->messageManager->addError(__('You cannot delete your own account.')); @@ -22,6 +27,11 @@ class Delete extends \Magento\User\Controller\Adminhtml\User return; } try { + $currentUserPassword = (string)$this->getRequest()->getPost(UserEdit::CURRENT_USER_PASSWORD_FIELD); + if (empty($currentUserPassword)) { + throw new AuthenticationException(__('You have entered an invalid password for current user.')); + } + $currentUser->performIdentityCheck($currentUserPassword); /** @var \Magento\User\Model\User $model */ $model = $this->_userFactory->create(); $model->setId($userId); diff --git a/app/code/Magento/User/Test/Unit/Controller/Adminhtml/User/DeleteTest.php b/app/code/Magento/User/Test/Unit/Controller/Adminhtml/User/DeleteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b88b28f0016964288bca46f793f0f5adb349dd39 --- /dev/null +++ b/app/code/Magento/User/Test/Unit/Controller/Adminhtml/User/DeleteTest.php @@ -0,0 +1,215 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\User\Test\Unit\Controller\Adminhtml\User; + +use Magento\Backend\Model\Auth\Session; +use Magento\Framework\Exception\AuthenticationException; + +/** + * Test class for \Magento\User\Controller\Adminhtml\User\Delete testing + */ +class DeleteTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\User\Controller\Adminhtml\User\Delete + */ + private $controller; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\RequestInterface + */ + private $requestMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\App\ResponseInterface + */ + private $responseMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|Session + */ + private $authSessionMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\ObjectManagerInterface + */ + private $objectManagerMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\User\Model\UserFactory + */ + private $userFactoryMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\User\Model\User + */ + private $userMock; + + /** + * @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Framework\Message\ManagerInterface + */ + private $messageManagerMock; + + /** + * @return void + */ + protected function setUp() + { + $this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManager\ObjectManager::class) + ->disableOriginalConstructor() + ->setMethods(['get', 'create']) + ->getMock(); + + $this->responseMock = $this->getMockBuilder(\Magento\Framework\App\ResponseInterface::class) + ->disableOriginalConstructor() + ->setMethods(['setRedirect']) + ->getMockForAbstractClass(); + + $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) + ->disableOriginalConstructor() + ->setMethods(['getPost']) + ->getMockForAbstractClass(); + + $this->authSessionMock = $this->getMockBuilder(Session::class) + ->disableOriginalConstructor() + ->setMethods(['getUser']) + ->getMock(); + + $this->userMock = $this->getMockBuilder(\Magento\User\Model\User::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'performIdentityCheck', 'delete']) + ->getMock(); + + $this->userFactoryMock = $this->getMockBuilder(\Magento\User\Model\UserFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) + ->disableOriginalConstructor() + ->getMock(); + + $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + + $this->controller = $objectManager->getObject( + \Magento\User\Controller\Adminhtml\User\Delete::class, + [ + 'request' => $this->requestMock, + 'response' => $this->responseMock, + 'objectManager' => $this->objectManagerMock, + 'messageManager' => $this->messageManagerMock, + 'userFactory' => $this->userFactoryMock, + ] + ); + } + + /** + * Test method \Magento\User\Controller\Adminhtml\User\Delete::execute + * + * @param string $currentUserPassword + * @param int $userId + * @param int $currentUserId + * @param string $resultMethod + * + * @dataProvider executeDataProvider + * @return void + * + */ + public function testExecute($currentUserPassword, $userId, $currentUserId, $resultMethod) + { + $currentUserMock = $this->userMock; + $this->authSessionMock->expects($this->any())->method('getUser')->will($this->returnValue($currentUserMock)); + + $currentUserMock->expects($this->any())->method('getId')->willReturn($currentUserId); + + $this->objectManagerMock + ->expects($this->any()) + ->method('get') + ->with(Session::class) + ->willReturn($this->authSessionMock); + + $this->requestMock->expects($this->any()) + ->method('getPost') + ->willReturnMap([ + ['user_id', $userId], + [\Magento\User\Block\User\Edit\Tab\Main::CURRENT_USER_PASSWORD_FIELD, $currentUserPassword], + ]); + + $userMock = clone $currentUserMock; + + $this->userFactoryMock->expects($this->any())->method('create')->will($this->returnValue($userMock)); + $this->responseMock->expects($this->any())->method('setRedirect')->willReturnSelf(); + $this->userMock->expects($this->any())->method('delete')->willReturnSelf(); + $this->messageManagerMock->expects($this->once())->method($resultMethod); + + $this->controller->execute(); + } + + /** + * @return void + */ + public function testEmptyPasswordThrowsException() + { + try { + $currentUserId = 1; + $userId = 2; + + $currentUserMock = $this->userMock; + $this->authSessionMock->expects($this->any()) + ->method('getUser') + ->will($this->returnValue($currentUserMock)); + + $currentUserMock->expects($this->any())->method('getId')->willReturn($currentUserId); + + $this->objectManagerMock + ->expects($this->any()) + ->method('get') + ->with(Session::class) + ->willReturn($this->authSessionMock); + + $this->requestMock->expects($this->any()) + ->method('getPost') + ->willReturnMap([ + ['user_id', $userId], + [\Magento\User\Block\User\Edit\Tab\Main::CURRENT_USER_PASSWORD_FIELD, ''], + ]); + + $this->controller->execute(); + } catch (AuthenticationException $e) { + $this->assertEquals($e->getMessage(), 'You have entered an invalid password for current user.'); + } + } + + /** + * Data Provider for execute method + * + * @return array + */ + public function executeDataProvider() + { + return [ + [ + 'currentUserPassword' => '123123q', + 'userId' => 1, + 'currentUserId' => 2, + 'resultMethod' => 'addSuccess', + ], + [ + 'currentUserPassword' => '123123q', + 'userId' => 0, + 'currentUserId' => 2, + 'resultMethod' => 'addError', + ], + [ + 'currentUserPassword' => '123123q', + 'userId' => 1, + 'currentUserId' => 1, + 'resultMethod' => 'addError', + ], + ]; + } +} diff --git a/app/code/Magento/User/composer.json b/app/code/Magento/User/composer.json index 755c47ac1147612e1e55b589e72afa705b39b0ce..4632e32bc17f90549f7271cadc5e1ef7ba08869e 100644 --- a/app/code/Magento/User/composer.json +++ b/app/code/Magento/User/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-user", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-authorization": "100.2.*", "magento/module-backend": "100.2.*", diff --git a/app/code/Magento/User/view/adminhtml/requirejs-config.js b/app/code/Magento/User/view/adminhtml/requirejs-config.js index 0424073c4d3db45d39394a95a99b9c697c88367d..44a5b331689bb6573c97da7f1d724e26bbea13ae 100644 --- a/app/code/Magento/User/view/adminhtml/requirejs-config.js +++ b/app/code/Magento/User/view/adminhtml/requirejs-config.js @@ -6,7 +6,8 @@ var config = { map: { '*': { - rolesTree: 'Magento_User/js/roles-tree' + rolesTree: 'Magento_User/js/roles-tree', + deleteUserAccount: 'Magento_User/js/delete-user-account' } } -}; \ No newline at end of file +}; diff --git a/app/code/Magento/User/view/adminhtml/templates/user/roles_grid_js.phtml b/app/code/Magento/User/view/adminhtml/templates/user/roles_grid_js.phtml index bac0dd0b3f869bd33ad5d7e12886f47daded00c2..40287595b937b5426c439e0c688adbf286b36d4e 100644 --- a/app/code/Magento/User/view/adminhtml/templates/user/roles_grid_js.phtml +++ b/app/code/Magento/User/view/adminhtml/templates/user/roles_grid_js.phtml @@ -71,3 +71,18 @@ require([ }); </script> + +<?php $editBlock = $block->getLayout()->getBlock('adminhtml.user.edit'); ?> +<?php if (is_object($editBlock)): ?> + <script type="text/x-magento-init"> + { + "[data-role=delete-user]" : { + "deleteUserAccount" : { + "message": "<?php echo $editBlock->escapeHtml($editBlock->getDeleteMessage()) ?>", + "url": "<?php /* @noEscape */ echo $editBlock->getDeleteUrl(); ?>", + "objId": "<?php echo $editBlock->escapeHtml($editBlock->getObjectId()) ?>" + } + } + } + </script> +<?php endif; ?> diff --git a/app/code/Magento/User/view/adminhtml/web/js/delete-user-account.js b/app/code/Magento/User/view/adminhtml/web/js/delete-user-account.js new file mode 100644 index 0000000000000000000000000000000000000000..b326c7aeb20d06953baf406ecf339d40f5be8bc4 --- /dev/null +++ b/app/code/Magento/User/view/adminhtml/web/js/delete-user-account.js @@ -0,0 +1,28 @@ +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +define([ + 'jquery' +], function ($) { + 'use strict'; + + var postData; + + return function (params, elem) { + + elem.on('click', function () { + + postData = { + 'data': { + 'user_id': params.objId, + 'current_password': $('[name="current_password"]').val() + } + }; + + if ($.validator.validateElement($('[name="current_password"]'))) { + window.deleteConfirm(params.message, params.url, postData); + } + }); + }; +}); diff --git a/app/code/Magento/Usps/Model/Carrier.php b/app/code/Magento/Usps/Model/Carrier.php index d12341659cb6d8ad1fc4acabe0b8052e346ca102..b2345a86bff4d6349035950373d1e7975076f513 100644 --- a/app/code/Magento/Usps/Model/Carrier.php +++ b/app/code/Magento/Usps/Model/Carrier.php @@ -426,9 +426,14 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C if (!$service) { $service = $r->getService(); } - if ($r->getContainer() == 'FLAT RATE BOX' || $r->getContainer() == 'FLAT RATE ENVELOPE') { + + if ( + strpos($r->getContainer(), 'FLAT RATE ENVELOPE') !== false || + strpos($r->getContainer(), 'FLAT RATE BOX') !== false + ) { $service = 'Priority'; } + $package->addChild('Service', $service); // no matter Letter, Flat or Parcel, use Parcel @@ -794,8 +799,15 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C 'first_class_mail_type' => ['LETTER' => __('Letter'), 'FLAT' => __('Flat'), 'PARCEL' => __('Parcel')], 'container' => [ 'VARIABLE' => __('Variable'), - 'FLAT RATE BOX' => __('Flat-Rate Box'), + 'SM FLAT RATE BOX' => __('Small Flat-Rate Box'), + 'MD FLAT RATE BOX' => __('Medium Flat-Rate Box'), + 'LG FLAT RATE BOX' => __('Large Flat-Rate Box'), 'FLAT RATE ENVELOPE' => __('Flat-Rate Envelope'), + 'SM FLAT RATE ENVELOPE' => __('Small Flat-Rate Envelope'), + 'WINDOW FLAT RATE ENVELOPE' => __('Window Flat-Rate Envelope'), + 'GIFT CARD FLAT RATE ENVELOPE' => __('Gift Card Flat-Rate Envelope'), + 'LEGAL FLAT RATE ENVELOPE' => __('Legal Flat-Rate Envelope'), + 'PADDED FLAT RATE ENVELOPE' => __('Padded Flat-Rate Envelope'), 'RECTANGULAR' => __('Rectangular'), 'NONRECTANGULAR' => __('Non-rectangular'), ], @@ -805,73 +817,103 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C 'filters' => [ 'within_us' => [ 'method' => [ - 'Priority Mail Express Flat Rate Envelope', - 'Priority Mail Express Flat Rate Envelope Hold For Pickup', - 'Priority Mail Flat Rate Envelope', - 'Priority Mail Large Flat Rate Box', - 'Priority Mail Medium Flat Rate Box', - 'Priority Mail Small Flat Rate Box', - 'Priority Mail Express Hold For Pickup', - 'Priority Mail Express', - 'Priority Mail', - 'Priority Mail Hold For Pickup', - 'Priority Mail Large Flat Rate Box Hold For Pickup', - 'Priority Mail Medium Flat Rate Box Hold For Pickup', - 'Priority Mail Small Flat Rate Box Hold For Pickup', - 'Priority Mail Flat Rate Envelope Hold For Pickup', - 'Priority Mail Small Flat Rate Envelope', - 'Priority Mail Small Flat Rate Envelope Hold For Pickup', - 'First-Class Package Service Hold For Pickup', - 'Retail Ground', - 'Media Mail', - 'First-Class Mail Large Envelope', - 'Priority Mail Express Sunday/Holiday Delivery', - 'Priority Mail Express Sunday/Holiday Delivery Flat Rate Envelope', - 'Priority Mail Express Sunday/Holiday Delivery Flat Rate Boxes', + '13', '27', '16', '22', '17', '28', '2', '3', '1', '33', '34', '35', + '36', '37', '42', '43', '53', '4', '6', '15', '23', '25', '57' ], ], 'from_us' => [ 'method' => [ - 'Priority Mail Express International Flat Rate Envelope', - 'Priority Mail International Flat Rate Envelope', - 'Priority Mail International Large Flat Rate Box', - 'Priority Mail International Medium Flat Rate Box', - 'Priority Mail International Small Flat Rate Box', - 'Priority Mail International Small Flat Rate Envelope', - 'Priority Mail Express International Flat Rate Boxes', - 'Global Express Guaranteed (GXG)', - 'USPS GXG Envelopes', - 'Priority Mail Express International', - 'Priority Mail International', - 'First-Class Mail International Letter', - 'First-Class Mail International Large Envelope', - 'First-Class Package International Service', + 'INT_10', 'INT_8', 'INT_11', 'INT_9', 'INT_16', 'INT_20', 'INT_4', + 'INT_12', 'INT_1', 'INT_2', 'INT_13', 'INT_14', 'INT_15' ], ], ], ], [ - 'containers' => ['FLAT RATE BOX'], + 'containers' => ['SM FLAT RATE BOX'], 'filters' => [ 'within_us' => [ - 'method' => [ - 'Priority Mail Large Flat Rate Box', - 'Priority Mail Medium Flat Rate Box', - 'Priority Mail Small Flat Rate Box', - 'Priority Mail International Large Flat Rate Box', - 'Priority Mail International Medium Flat Rate Box', - 'Priority Mail International Small Flat Rate Box', - 'Priority Mail Express Sunday/Holiday Delivery Flat Rate Boxes', - ], + 'method' => ['28', '57'], ], 'from_us' => [ - 'method' => [ - 'Priority Mail International Large Flat Rate Box', - 'Priority Mail International Medium Flat Rate Box', - 'Priority Mail International Small Flat Rate Box', - 'Priority Mail International DVD Flat Rate priced box', - 'Priority Mail International Large Video Flat Rate priced box', - ], + 'method' => ['INT_16', 'INT_24'], + ], + ] + ], + [ + 'containers' => ['MD FLAT RATE BOX'], + 'filters' => [ + 'within_us' => [ + 'method' => ['17', '57'], + ], + 'from_us' => [ + 'method' => ['INT_9', 'INT_24'], + ], + ] + ], + [ + 'containers' => ['LG FLAT RATE BOX'], + 'filters' => [ + 'within_us' => [ + 'method' => ['22', '57'], + ], + 'from_us' => [ + 'method' => ['INT_11', 'INT_24', 'INT_25'], + ], + ] + ], + [ + 'containers' => ['SM FLAT RATE ENVELOPE'], + 'filters' => [ + 'within_us' => [ + 'method' => ['42', '43'], + ], + 'from_us' => [ + 'method' => ['INT_20'], + ], + ] + ], + [ + 'containers' => ['WINDOW FLAT RATE ENVELOPE'], + 'filters' => [ + 'within_us' => [ + 'method' => ['40', '41'], + ], + 'from_us' => [ + 'method' => ['INT_19'], + ], + ] + ], + [ + 'containers' => ['GIFT CARD FLAT RATE ENVELOPE'], + 'filters' => [ + 'within_us' => [ + 'method' => ['38', '39'], + ], + 'from_us' => [ + 'method' => ['INT_18'], + ], + ] + ], + [ + 'containers' => ['PADDED FLAT RATE ENVELOPE'], + 'filters' => [ + 'within_us' => [ + 'method' => ['62', '63', '64', '46', '29'], + ], + 'from_us' => [ + 'method' => ['INT_27', 'INT_23'], + ], + ] + ], + [ + 'containers' => ['LEGAL FLAT RATE ENVELOPE'], + 'filters' => [ + 'within_us' => [ + 'method' => ['44', '45', '30', '31', '32'], + ], + 'from_us' => [ + 'method' => ['INT_17', 'INT_22'], ], ] ], @@ -879,30 +921,11 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C 'containers' => ['FLAT RATE ENVELOPE'], 'filters' => [ 'within_us' => [ - 'method' => [ - 'Priority Mail Flat Rate Envelope', - 'Priority Mail Express Flat Rate Envelope', - 'Priority Mail Express Flat Rate Envelope Hold For Pickup', - 'Priority Mail Flat Rate Envelope', - 'First-Class Mail Large Envelope', - 'Priority Mail Flat Rate Envelope Hold For Pickup', - 'Priority Mail Small Flat Rate Envelope', - 'Priority Mail Small Flat Rate Envelope Hold For Pickup', - 'Priority Mail Express Sunday/Holiday Delivery Flat Rate Envelope', - 'Priority Mail Express Padded Flat Rate Envelope', - ], + 'method' => ['16', '13', '27', '16', '15', '37', '42', '43', '25', '62'], ], 'from_us' => [ 'method' => [ - 'Priority Mail Express International Flat Rate Envelope', - 'Priority Mail International Flat Rate Envelope', - 'First-Class Mail International Large Envelope', - 'Priority Mail International Small Flat Rate Envelope', - 'Priority Mail Express International Legal Flat Rate Envelope', - 'Priority Mail International Gift Card Flat Rate Envelope', - 'Priority Mail International Window Flat Rate Envelope', - 'Priority Mail International Legal Flat Rate Envelope', - 'Priority Mail Express International Padded Flat Rate Envelope', + 'INT_10', 'INT_8', 'INT_14', 'INT_20', 'INT_17', 'INT_18', 'INT_19', 'INT_22', 'INT_27' ], ], ] @@ -911,22 +934,10 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C 'containers' => ['RECTANGULAR'], 'filters' => [ 'within_us' => [ - 'method' => [ - 'Priority Mail Express', - 'Priority Mail', - 'Retail Ground', - 'Media Mail', - 'Library Mail', - 'First-Class Package Service', - ], + 'method' => ['3', '1', '4', '6', '7', '61'], ], 'from_us' => [ - 'method' => [ - 'USPS GXG Envelopes', - 'Priority Mail Express International', - 'Priority Mail International', - 'First-Class Package International Service', - ], + 'method' => ['INT_12', 'INT_1', 'INT_2', 'INT_15'], ], ] ], @@ -934,21 +945,10 @@ class Carrier extends AbstractCarrierOnline implements \Magento\Shipping\Model\C 'containers' => ['NONRECTANGULAR'], 'filters' => [ 'within_us' => [ - 'method' => [ - 'Priority Mail Express', - 'Priority Mail', - 'Retail Ground', - 'Media Mail', - 'Library Mail', - ], + 'method' => ['3', '1', '4', '6', '7'], ], 'from_us' => [ - 'method' => [ - 'Global Express Guaranteed (GXG)', - 'Priority Mail Express International', - 'Priority Mail International', - 'First-Class Package International Service', - ], + 'method' => ['INT_4', 'INT_1', 'INT_2', 'INT_15'], ], ] ], diff --git a/app/code/Magento/Usps/composer.json b/app/code/Magento/Usps/composer.json index 5baccddf4884307f4ebdeed9199fedb00debf4bc..15e93d0bea4fb6d0e89f21f180b2c91486c8f5aa 100644 --- a/app/code/Magento/Usps/composer.json +++ b/app/code/Magento/Usps/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-usps", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-shipping": "100.2.*", "magento/module-directory": "100.2.*", diff --git a/app/code/Magento/Variable/composer.json b/app/code/Magento/Variable/composer.json index 21f3ec1dd467f240c609d5931ffa5cbde8dc3e15..e4b06d0edb34ab25d4d1279fb463a4f630ee77e9 100644 --- a/app/code/Magento/Variable/composer.json +++ b/app/code/Magento/Variable/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-variable", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-backend": "100.2.*", "magento/module-email": "100.2.*", "magento/module-store": "100.2.*", diff --git a/app/code/Magento/Vault/Model/Method/Vault.php b/app/code/Magento/Vault/Model/Method/Vault.php index 41a2d6b0e26efcd1c6745f5d5fbc2842ae6c8513..ca6fe06f91eef8ce52e5f0c5b0cdbd7f439f5710 100644 --- a/app/code/Magento/Vault/Model/Method/Vault.php +++ b/app/code/Magento/Vault/Model/Method/Vault.php @@ -5,17 +5,16 @@ */ namespace Magento\Vault\Model\Method; -use Magento\Framework\DataObject; use Magento\Framework\Event\ManagerInterface; use Magento\Framework\ObjectManagerInterface; use Magento\Payment\Gateway\Command; -use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; use Magento\Payment\Gateway\Config\ValueHandlerPoolInterface; use Magento\Payment\Gateway\ConfigFactoryInterface; use Magento\Payment\Gateway\ConfigInterface; use Magento\Payment\Model\InfoInterface; use Magento\Payment\Model\MethodInterface; use Magento\Payment\Observer\AbstractDataAssignObserver; +use Magento\Sales\Api\Data\OrderPaymentExtensionInterfaceFactory; use Magento\Sales\Api\Data\OrderPaymentInterface; use Magento\Sales\Model\Order\Payment; use Magento\Vault\Api\Data\PaymentTokenInterface; @@ -275,7 +274,12 @@ final class Vault implements VaultPaymentInterface */ public function canUseInternal() { - return $this->getVaultProvider()->canUseInternal(); + $isInternalAllowed = $this->getConfiguredValue('can_use_internal'); + // if config has't been specified for Vault, need to check payment provider option + if ($isInternalAllowed === null) { + return $this->getVaultProvider()->canUseInternal(); + } + return (bool) $isInternalAllowed; } /** diff --git a/app/code/Magento/Vault/Model/Ui/Adminhtml/TokensConfigProvider.php b/app/code/Magento/Vault/Model/Ui/Adminhtml/TokensConfigProvider.php index 98e7728f732fe51bcea79e555c5333a9cbd86684..f3a2cbfa78739d0ee748b5e38a01176ae6ed7e67 100644 --- a/app/code/Magento/Vault/Model/Ui/Adminhtml/TokensConfigProvider.php +++ b/app/code/Magento/Vault/Model/Ui/Adminhtml/TokensConfigProvider.php @@ -221,9 +221,11 @@ final class TokensConfigProvider */ private function getPaymentTokenEntityId() { - return $this->getPaymentTokenManagement() - ->getByPaymentId($this->getOrderPaymentEntityId()) - ->getEntityId(); + $paymentToken = $this->getPaymentTokenManagement()->getByPaymentId($this->getOrderPaymentEntityId()); + if ($paymentToken === null) { + throw new NoSuchEntityException(__('No available payment tokens for specified order payment.')); + } + return $paymentToken->getEntityId(); } /** diff --git a/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php b/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php index 8bded498dab765a442ced658ba2562db81ca5b7e..ade93c9367858eafb1cfdf64035d2d4f9354e8a0 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/Method/VaultTest.php @@ -8,6 +8,8 @@ namespace Magento\Vault\Test\Unit\Model\Method; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Payment\Gateway\Command\CommandManagerInterface; use Magento\Payment\Gateway\Command\CommandManagerPoolInterface; +use Magento\Payment\Gateway\Config\ValueHandlerInterface; +use Magento\Payment\Gateway\Config\ValueHandlerPoolInterface; use Magento\Payment\Gateway\ConfigInterface; use Magento\Payment\Model\InfoInterface; use Magento\Payment\Model\MethodInterface; @@ -19,6 +21,7 @@ use Magento\Vault\Api\Data\PaymentTokenInterface; use Magento\Vault\Api\PaymentTokenManagementInterface; use Magento\Vault\Model\Method\Vault; use Magento\Vault\Model\VaultPaymentInterface; +use PHPUnit_Framework_MockObject_MockObject as MockObject; /** * Class VaultTest @@ -31,9 +34,15 @@ class VaultTest extends \PHPUnit_Framework_TestCase */ private $objectManager; + /** + * @var MethodInterface|MockObject + */ + private $vaultProvider; + public function setUp() { $this->objectManager = new ObjectManager($this); + $this->vaultProvider = $this->getMock(MethodInterface::class); } /** @@ -137,8 +146,6 @@ class VaultTest extends \PHPUnit_Framework_TestCase $commandManagerPool = $this->getMock(CommandManagerPoolInterface::class); $commandManager = $this->getMock(CommandManagerInterface::class); - $vaultProvider = $this->getMock(MethodInterface::class); - $tokenManagement = $this->getMock(PaymentTokenManagementInterface::class); $token = $this->getMock(PaymentTokenInterface::class); @@ -161,7 +168,7 @@ class VaultTest extends \PHPUnit_Framework_TestCase ->method('setVaultPaymentToken') ->with($token); - $vaultProvider->expects(static::atLeastOnce()) + $this->vaultProvider->expects(static::atLeastOnce()) ->method('getCode') ->willReturn($vaultProviderCode); $commandManagerPool->expects(static::once()) @@ -188,7 +195,7 @@ class VaultTest extends \PHPUnit_Framework_TestCase [ 'tokenManagement' => $tokenManagement, 'commandManagerPool' => $commandManagerPool, - 'vaultProvider' => $vaultProvider + 'vaultProvider' => $this->vaultProvider ] ); $model->authorize($paymentModel, $amount); @@ -235,10 +242,9 @@ class VaultTest extends \PHPUnit_Framework_TestCase { $storeId = 1; $quote = $this->getMockForAbstractClass(CartInterface::class); - $vaultProvider = $this->getMockForAbstractClass(MethodInterface::class); $config = $this->getMockForAbstractClass(ConfigInterface::class); - $vaultProvider->expects(static::once()) + $this->vaultProvider->expects(static::once()) ->method('isAvailable') ->with($quote) ->willReturn($isAvailableProvider); @@ -255,7 +261,7 @@ class VaultTest extends \PHPUnit_Framework_TestCase /** @var Vault $model */ $model = $this->objectManager->getObject(Vault::class, [ 'config' => $config, - 'vaultProvider' => $vaultProvider + 'vaultProvider' => $this->vaultProvider ]); $actual = $model->isAvailable($quote); static::assertEquals($expected, $actual); @@ -281,11 +287,9 @@ class VaultTest extends \PHPUnit_Framework_TestCase public function testIsAvailableWithoutQuote() { $quote = null; - - $vaultProvider = $this->getMockForAbstractClass(MethodInterface::class); $config = $this->getMockForAbstractClass(ConfigInterface::class); - $vaultProvider->expects(static::once()) + $this->vaultProvider->expects(static::once()) ->method('isAvailable') ->with($quote) ->willReturn(true); @@ -298,8 +302,60 @@ class VaultTest extends \PHPUnit_Framework_TestCase /** @var Vault $model */ $model = $this->objectManager->getObject(Vault::class, [ 'config' => $config, - 'vaultProvider' => $vaultProvider + 'vaultProvider' => $this->vaultProvider ]); static::assertFalse($model->isAvailable($quote)); } + + /** + * @covers \Magento\Vault\Model\Method\Vault::canUseInternal + * @param bool|null $configValue + * @param bool|null $paymentValue + * @param bool $expected + * @dataProvider internalUsageDataProvider + */ + public function testCanUseInternal($configValue, $paymentValue, $expected) + { + $handlerPool = $this->getMock(ValueHandlerPoolInterface::class); + $handler = $this->getMock(ValueHandlerInterface::class); + + $handlerPool->expects(static::once()) + ->method('get') + ->with('can_use_internal') + ->willReturn($handler); + + $handler->expects(static::once()) + ->method('handle') + ->with(['field' => 'can_use_internal'], null) + ->willReturn($configValue); + + $this->vaultProvider->expects(static::any()) + ->method('canUseInternal') + ->willReturn($paymentValue); + + /** @var Vault $model */ + $model = $this->objectManager->getObject(Vault::class, [ + 'vaultProvider' => $this->vaultProvider, + 'valueHandlerPool' => $handlerPool, + ]); + static::assertEquals($expected, $model->canUseInternal()); + } + + /** + * Get list of variations for testing canUseInternal method + * @return array + */ + public function internalUsageDataProvider() + { + return [ + ['configValue' => true, 'paymentValue' => true, 'expected' => true], + ['configValue' => true, 'paymentValue' => null, 'expected' => true], + ['configValue' => true, 'paymentValue' => false, 'expected' => true], + ['configValue' => false, 'paymentValue' => true, 'expected' => false], + ['configValue' => false, 'paymentValue' => false, 'expected' => false], + ['configValue' => null, 'paymentValue' => true, 'expected' => true], + ['configValue' => null, 'paymentValue' => false, 'expected' => false], + ['configValue' => null, 'paymentValue' => null, 'expected' => false], + ]; + } } diff --git a/app/code/Magento/Vault/Test/Unit/Model/Ui/Adminhtml/TokensConfigProviderTest.php b/app/code/Magento/Vault/Test/Unit/Model/Ui/Adminhtml/TokensConfigProviderTest.php index 38948b004eebc358ce82eddf8bf2af88e1685c8c..bbac491386c44a231a1cc650a2ba3acfca791282 100644 --- a/app/code/Magento/Vault/Test/Unit/Model/Ui/Adminhtml/TokensConfigProviderTest.php +++ b/app/code/Magento/Vault/Test/Unit/Model/Ui/Adminhtml/TokensConfigProviderTest.php @@ -13,6 +13,7 @@ use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Intl\DateTimeFactory; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\TestFramework\Unit\Matcher\MethodInvokedAtIndex; use Magento\Payment\Helper\Data; use Magento\Sales\Api\Data\OrderInterface; @@ -29,7 +30,6 @@ use Magento\Vault\Model\Ui\TokenUiComponentInterface; use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; use Magento\Vault\Model\VaultPaymentInterface; use PHPUnit_Framework_MockObject_MockObject as MockObject; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; /** * Class TokensConfigProviderTest @@ -104,11 +104,21 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase */ private $orderRepository; + /** + * @var TokenUiComponentProviderInterface|MockObject + */ + private $tokenComponentProvider; + /** * @var ObjectManager */ private $objectManager; + /** + * @var TokensConfigProvider + */ + private $configProvider; + protected function setUp() { $this->paymentTokenRepository = $this->getMockBuilder(PaymentTokenRepositoryInterface::class) @@ -138,6 +148,38 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase $this->vaultPayment = $this->getMockForAbstractClass(VaultPaymentInterface::class); $this->objectManager = new ObjectManager($this); + + $this->initStoreMock(); + + $this->tokenComponentProvider = $this->getMock(TokenUiComponentProviderInterface::class); + + $this->configProvider = new TokensConfigProvider( + $this->session, + $this->paymentTokenRepository, + $this->filterBuilder, + $this->searchCriteriaBuilder, + $this->storeManager, + $this->dateTimeFactory, + [ + self::VAULT_PROVIDER_CODE => $this->tokenComponentProvider + ] + ); + + $this->objectManager->setBackwardCompatibleProperty( + $this->configProvider, + 'paymentDataHelper', + $this->paymentDataHelper + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->configProvider, + 'paymentTokenManagement', + $this->paymentTokenManagement + ); + $this->objectManager->setBackwardCompatibleProperty( + $this->configProvider, + 'orderRepository', + $this->orderRepository + ); } /** @@ -147,8 +189,6 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase { $customerId = 1; - $this->initStoreMock(); - $this->session->expects(static::once()) ->method('getCustomerId') ->willReturn($customerId); @@ -170,7 +210,7 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase $token = $this->getMockBuilder(PaymentTokenInterface::class) ->getMockForAbstractClass(); - list($tokenUiComponent, $tokenUiComponentProvider) = $this->getTokenUiComponentProvider($token); + $tokenUiComponent = $this->getTokenUiComponentProvider($token); $searchCriteria = $this->getSearchCriteria($customerId, self::ENTITY_ID, self::VAULT_PROVIDER_CODE); @@ -197,25 +237,7 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase ->method('getItems') ->willReturn([$token]); - $configProvider = new TokensConfigProvider( - $this->session, - $this->paymentTokenRepository, - $this->filterBuilder, - $this->searchCriteriaBuilder, - $this->storeManager, - $this->dateTimeFactory, - [ - self::VAULT_PROVIDER_CODE => $tokenUiComponentProvider - ] - ); - - $this->objectManager->setBackwardCompatibleProperty( - $configProvider, - 'paymentDataHelper', - $this->paymentDataHelper - ); - - static::assertEquals([$tokenUiComponent], $configProvider->getTokensComponents(self::VAULT_PAYMENT_CODE)); + static::assertEquals([$tokenUiComponent], $this->configProvider->getTokensComponents(self::VAULT_PAYMENT_CODE)); } /** @@ -263,7 +285,7 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase ->method('getEntityId') ->willReturn(self::ENTITY_ID); - list($tokenUiComponent, $tokenUiComponentProvider) = $this->getTokenUiComponentProvider($token); + $tokenUiComponent = $this->getTokenUiComponentProvider($token); $searchCriteria = $this->getSearchCriteria($customerId, self::ENTITY_ID, self::VAULT_PROVIDER_CODE); @@ -290,35 +312,7 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase ->method('getItems') ->willReturn([$token]); - $configProvider = new TokensConfigProvider( - $this->session, - $this->paymentTokenRepository, - $this->filterBuilder, - $this->searchCriteriaBuilder, - $this->storeManager, - $this->dateTimeFactory, - [ - self::VAULT_PROVIDER_CODE => $tokenUiComponentProvider - ] - ); - - $this->objectManager->setBackwardCompatibleProperty( - $configProvider, - 'paymentDataHelper', - $this->paymentDataHelper - ); - $this->objectManager->setBackwardCompatibleProperty( - $configProvider, - 'paymentTokenManagement', - $this->paymentTokenManagement - ); - $this->objectManager->setBackwardCompatibleProperty( - $configProvider, - 'orderRepository', - $this->orderRepository - ); - - static::assertEquals([$tokenUiComponent], $configProvider->getTokensComponents(self::VAULT_PAYMENT_CODE)); + static::assertEquals([$tokenUiComponent], $this->configProvider->getTokensComponents(self::VAULT_PAYMENT_CODE)); } /** @@ -330,8 +324,6 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase { $customerId = null; - $this->initStoreMock(); - $this->session->expects(static::once()) ->method('getCustomerId') ->willReturn($customerId); @@ -366,35 +358,7 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase $this->searchCriteriaBuilder->expects(self::never()) ->method('addFilters'); - $configProvider = new TokensConfigProvider( - $this->session, - $this->paymentTokenRepository, - $this->filterBuilder, - $this->searchCriteriaBuilder, - $this->storeManager, - $this->dateTimeFactory, - [ - self::VAULT_PROVIDER_CODE => $this->getMock(TokenUiComponentProviderInterface::class) - ] - ); - - $this->objectManager->setBackwardCompatibleProperty( - $configProvider, - 'paymentDataHelper', - $this->paymentDataHelper - ); - $this->objectManager->setBackwardCompatibleProperty( - $configProvider, - 'paymentTokenManagement', - $this->paymentTokenManagement - ); - $this->objectManager->setBackwardCompatibleProperty( - $configProvider, - 'orderRepository', - $this->orderRepository - ); - - static::assertEmpty($configProvider->getTokensComponents(self::VAULT_PAYMENT_CODE)); + static::assertEmpty($this->configProvider->getTokensComponents(self::VAULT_PAYMENT_CODE)); } /** @@ -453,18 +417,66 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase static::assertEmpty($configProvider->getTokensComponents(self::VAULT_PAYMENT_CODE)); } + /** + * @covers \Magento\Vault\Model\Ui\Adminhtml\TokensConfigProvider::getTokensComponents + */ + public function testGetTokensComponentsForGuestCustomerWithoutStoredTokens() + { + $this->session->expects(static::once()) + ->method('getCustomerId') + ->willReturn(null); + + $this->paymentDataHelper->expects(static::once()) + ->method('getMethodInstance') + ->with(self::VAULT_PAYMENT_CODE) + ->willReturn($this->vaultPayment); + + $this->vaultPayment->expects(static::once()) + ->method('isActive') + ->with(self::STORE_ID) + ->willReturn(true); + $this->vaultPayment->expects(static::once()) + ->method('getProviderCode') + ->willReturn(self::VAULT_PROVIDER_CODE); + + $this->session->expects(static::once()) + ->method('getReordered') + ->willReturn(self::ORDER_ID); + $this->orderRepository->expects(static::once()) + ->method('get') + ->with(self::ORDER_ID) + ->willReturn($this->getOrderMock()); + + $this->paymentTokenManagement->expects(static::once()) + ->method('getByPaymentId') + ->with(self::ORDER_PAYMENT_ENTITY_ID) + ->willReturn(null); + + $this->filterBuilder->expects(static::once()) + ->method('setField') + ->with(PaymentTokenInterface::ENTITY_ID) + ->willReturnSelf(); + $this->filterBuilder->expects(static::never()) + ->method('setValue'); + + $this->searchCriteriaBuilder->expects(static::never()) + ->method('addFilters'); + + static::assertEmpty($this->configProvider->getTokensComponents(self::VAULT_PAYMENT_CODE)); + } + /** * Create mock object for store */ private function initStoreMock() { $this->store = $this->getMock(StoreInterface::class); - $this->store->expects(static::once()) + $this->store->expects(static::any()) ->method('getId') ->willReturn(self::STORE_ID); $this->storeManager = $this->getMock(StoreManagerInterface::class); - $this->storeManager->expects(static::once()) + $this->storeManager->expects(static::any()) ->method('getStore') ->with(null) ->willReturn($this->store); @@ -476,39 +488,37 @@ class TokensConfigProviderTest extends \PHPUnit_Framework_TestCase */ private function getOrderMock() { - /** @var OrderInterface|MockObject $orderMock */ - $orderMock = $this->getMockBuilder(OrderInterface::class) + /** @var OrderInterface|MockObject $order */ + $order = $this->getMockBuilder(OrderInterface::class) ->getMockForAbstractClass(); - /** @var OrderPaymentInterface|MockObject $orderPaymentMock */ - $orderPaymentMock = $this->getMockBuilder(OrderPaymentInterface::class) + /** @var OrderPaymentInterface|MockObject $orderPayment */ + $orderPayment = $this->getMockBuilder(OrderPaymentInterface::class) ->getMockForAbstractClass(); - $orderMock->expects(static::once()) + $order->expects(static::once()) ->method('getPayment') - ->willReturn($orderPaymentMock); - $orderPaymentMock->expects(static::once()) + ->willReturn($orderPayment); + $orderPayment->expects(static::once()) ->method('getEntityId') ->willReturn(self::ORDER_PAYMENT_ENTITY_ID); - return $orderMock; + return $order; } /** * Get mock for token ui component provider * @param PaymentTokenInterface $token - * @return array + * @return TokenUiComponentInterface|MockObject */ private function getTokenUiComponentProvider($token) { $tokenUiComponent = $this->getMock(TokenUiComponentInterface::class); - - $tokenUiComponentProvider = $this->getMock(TokenUiComponentProviderInterface::class); - $tokenUiComponentProvider->expects(static::once()) + $this->tokenComponentProvider->expects(static::once()) ->method('getComponentForToken') ->with($token) ->willReturn($tokenUiComponent); - return [$tokenUiComponent, $tokenUiComponentProvider]; + return $tokenUiComponent; } /** diff --git a/app/code/Magento/Vault/composer.json b/app/code/Magento/Vault/composer.json index 74dd568e3b45ee4e3e7f9e235d3389191b7a93e4..b2edf040674d83f6335d407089201b46fe16ecce 100644 --- a/app/code/Magento/Vault/composer.json +++ b/app/code/Magento/Vault/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-vault", "description": "", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*", "magento/module-sales": "100.2.*", "magento/module-store": "100.2.*", diff --git a/app/code/Magento/Vault/view/frontend/layout/customer_account.xml b/app/code/Magento/Vault/view/frontend/layout/customer_account.xml index 2042721d9d387ed0354f8163738a83e1f94d1710..01edaa1f093478d0eb0eb03f42bf98815851e16b 100644 --- a/app/code/Magento/Vault/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Vault/view/frontend/layout/customer_account.xml @@ -8,10 +8,11 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-my-credit-cards-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-my-credit-cards-link"> <arguments> <argument name="path" xsi:type="string">vault/cards/listaction</argument> <argument name="label" xsi:type="string" translate="true">Stored Payment Methods</argument> + <argument name="sortOrder" xsi:type="number">160</argument> </arguments> </block> </referenceBlock> diff --git a/app/code/Magento/Vault/view/frontend/web/js/view/payment/vault.js b/app/code/Magento/Vault/view/frontend/web/js/view/payment/vault.js index 05d56e305f23eea9473c5c3da2d0a130ca2c42a5..3015755191d37d3159158a9505a5b3c9bf5c7bf8 100644 --- a/app/code/Magento/Vault/view/frontend/web/js/view/payment/vault.js +++ b/app/code/Magento/Vault/view/frontend/web/js/view/payment/vault.js @@ -4,25 +4,32 @@ */ /*browser:true*/ /*global define*/ -define( - [ - 'underscore', - 'uiComponent', - 'Magento_Checkout/js/model/payment/renderer-list' - ], - function ( - _, - Component, - rendererList - ) { - 'use strict'; +define([ + 'underscore', + 'uiComponent', + 'Magento_Checkout/js/model/payment/renderer-list', + 'uiLayout', + 'uiRegistry' +], function (_, Component, rendererList, layout, registry) { + 'use strict'; + var vaultGroupName = 'vaultGroup'; + + layout([{ + name: vaultGroupName, + component: 'Magento_Checkout/js/model/payment/method-group', + alias: 'vault', + sortOrder: 10 + }]); + + registry.get(vaultGroupName, function (vaultGroup) { _.each(window.checkoutConfig.payment.vault, function (config, index) { rendererList.push( { type: index, config: config.config, component: config.component, + group: vaultGroup, /** * Custom payment method types comparator @@ -37,10 +44,10 @@ define( } ); }); + }); - /** - * Add view logic here if needed - */ - return Component.extend({}); - } -); + /** + * Add view logic here if needed + */ + return Component.extend({}); +}); diff --git a/app/code/Magento/Version/composer.json b/app/code/Magento/Version/composer.json index 3a972432101154fa2c4f167473fc6f4d6aa113a0..4396f1fb2b3bf4e114dd2ab4dfbd37eb7c2ffd7e 100644 --- a/app/code/Magento/Version/composer.json +++ b/app/code/Magento/Version/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-version", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*" }, "type": "magento2-module", diff --git a/app/code/Magento/Webapi/composer.json b/app/code/Magento/Webapi/composer.json index f2796df70a8fae522a34d0c94e079afd9f525f9c..6fc712722adddf10c16e4add70913a62c2ef2bc7 100644 --- a/app/code/Magento/Webapi/composer.json +++ b/app/code/Magento/Webapi/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-webapi", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-authorization": "100.2.*", "magento/module-integration": "100.2.*", diff --git a/app/code/Magento/WebapiSecurity/composer.json b/app/code/Magento/WebapiSecurity/composer.json index 506975f1aa188d4f0d90f8d2f0484153998f2c39..0f4ef7b3dc8839518bcc2982e9975c53d99a6c1b 100644 --- a/app/code/Magento/WebapiSecurity/composer.json +++ b/app/code/Magento/WebapiSecurity/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-webapi-security", "description": "WebapiSecurity module provides option to loosen security on some webapi resources.", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-webapi": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/app/code/Magento/Weee/composer.json b/app/code/Magento/Weee/composer.json index 035d66e5a96b416f1f5e3140ef9e45e299c1b52a..70fd4a25d0310d94d4254a8079755eab01ad610f 100644 --- a/app/code/Magento/Weee/composer.json +++ b/app/code/Magento/Weee/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-weee", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-catalog": "101.1.*", "magento/module-tax": "100.2.*", diff --git a/app/code/Magento/Widget/composer.json b/app/code/Magento/Widget/composer.json index f59c4180eb2bb56522ae20f3615da92dd0c60370..b5a5e9e5569e3c62641cfc2d3cbc34030229020b 100644 --- a/app/code/Magento/Widget/composer.json +++ b/app/code/Magento/Widget/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-widget", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-cms": "101.1.*", "magento/module-backend": "100.2.*", diff --git a/app/code/Magento/Wishlist/Block/Link.php b/app/code/Magento/Wishlist/Block/Link.php index fea2ad5941c5893ceaff7dac1e69bf9c9d133db2..51e59998339d8b809182851b940f4e156c61c0e9 100644 --- a/app/code/Magento/Wishlist/Block/Link.php +++ b/app/code/Magento/Wishlist/Block/Link.php @@ -9,12 +9,14 @@ */ namespace Magento\Wishlist\Block; +use Magento\Customer\Block\Account\SortLinkInterface; + /** * Class Link * * @SuppressWarnings(PHPMD.DepthOfInheritance) */ -class Link extends \Magento\Framework\View\Element\Html\Link +class Link extends \Magento\Framework\View\Element\Html\Link implements SortLinkInterface { /** * Template name @@ -68,4 +70,12 @@ class Link extends \Magento\Framework\View\Element\Html\Link { return __('My Wish List'); } + + /** + * {@inheritdoc} + */ + public function getSortOrder() + { + return $this->getData(self::SORT_ORDER); + } } diff --git a/app/code/Magento/Wishlist/Controller/Index/Cart.php b/app/code/Magento/Wishlist/Controller/Index/Cart.php index f15e89d16c9b4f9bba2adbe2a23621417e255f1a..a907abcd1c56c829dedaddbd065fff3cc1fede08 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Cart.php +++ b/app/code/Magento/Wishlist/Controller/Index/Cart.php @@ -113,6 +113,7 @@ class Cart extends \Magento\Wishlist\Controller\AbstractIndex * @return \Magento\Framework\Controller\ResultInterface * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function execute() { @@ -137,6 +138,10 @@ class Cart extends \Magento\Wishlist\Controller\AbstractIndex // Set qty $qty = $this->getRequest()->getParam('qty'); + $postQty = $this->getRequest()->getPostValue('qty'); + if ($postQty !== null && $qty !== $postQty) { + $qty = $postQty; + } if (is_array($qty)) { if (isset($qty[$itemId])) { $qty = $qty[$itemId]; diff --git a/app/code/Magento/Wishlist/Pricing/ConfiguredPrice/ConfigurableProduct.php b/app/code/Magento/Wishlist/Pricing/ConfiguredPrice/ConfigurableProduct.php index 6d8e4bcd2fd1c41844cd4398043b61dec4ff36ff..be995258794cc05f1fca4a2124cac04e8c0e1c87 100644 --- a/app/code/Magento/Wishlist/Pricing/ConfiguredPrice/ConfigurableProduct.php +++ b/app/code/Magento/Wishlist/Pricing/ConfiguredPrice/ConfigurableProduct.php @@ -21,15 +21,12 @@ class ConfigurableProduct extends FinalPrice implements ConfiguredPriceInterface */ public function getValue() { - $result = 0.; /** @var \Magento\Wishlist\Model\Item\Option $customOption */ $customOption = $this->getProduct()->getCustomOption('simple_product'); - if ($customOption) { - /** @var \Magento\Framework\Pricing\PriceInfoInterface $priceInfo */ - $priceInfo = $customOption->getProduct()->getPriceInfo(); - $result = $priceInfo->getPrice(self::PRICE_CODE)->getValue(); - } - return max(0, $result); + $product = $customOption ? $customOption->getProduct() : $this->getProduct(); + $price = $product->getPriceInfo()->getPrice(self::PRICE_CODE)->getValue(); + + return max(0, $price); } /** diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php index e1a7cd448569f05060d9995ee5d02de41630f680..743fc39e4b43d7819af97fc97c84efb9f3f38013 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php @@ -159,7 +159,7 @@ class CartTest extends \PHPUnit_Framework_TestCase $this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) ->disableOriginalConstructor() - ->setMethods(['getParams', 'getParam', 'isAjax']) + ->setMethods(['getParams', 'getParam', 'isAjax', 'getPostValue']) ->getMockForAbstractClass(); $this->redirectMock = $this->getMockBuilder(\Magento\Framework\App\Response\RedirectInterface::class) @@ -916,4 +916,176 @@ class CartTest extends \PHPUnit_Framework_TestCase $this->assertSame($this->resultRedirectMock, $this->model->execute()); } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testExecuteWithEditQuantity() + { + $itemId = 2; + $wishlistId = 1; + $qty = 1; + $postQty = 2; + $productId = 4; + $indexUrl = 'index_url'; + $configureUrl = 'configure_url'; + $options = [5 => 'option']; + $params = ['item' => $itemId, 'qty' => $qty]; + + $this->formKeyValidator->expects($this->once()) + ->method('validate') + ->with($this->requestMock) + ->willReturn(true); + + $itemMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item::class) + ->disableOriginalConstructor() + ->setMethods( + [ + 'load', + 'getId', + 'getWishlistId', + 'setQty', + 'setOptions', + 'getBuyRequest', + 'mergeBuyRequest', + 'addToCart', + 'getProduct', + 'getProductId', + ] + ) + ->getMock(); + + $this->requestMock->expects($this->at(0)) + ->method('getParam') + ->with('item', null) + ->willReturn($itemId); + $this->itemFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($itemMock); + + $itemMock->expects($this->once()) + ->method('load') + ->with($itemId, null) + ->willReturnSelf(); + $itemMock->expects($this->exactly(2)) + ->method('getId') + ->willReturn($itemId); + $itemMock->expects($this->once()) + ->method('getWishlistId') + ->willReturn($wishlistId); + + $wishlistMock = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->wishlistProviderMock->expects($this->once()) + ->method('getWishlist') + ->with($wishlistId) + ->willReturn($wishlistMock); + + $this->requestMock->expects($this->at(1)) + ->method('getParam') + ->with('qty', null) + ->willReturn($qty); + + $this->requestMock->expects($this->once()) + ->method('getPostValue') + ->with('qty') + ->willReturn($postQty); + + $this->quantityProcessorMock->expects($this->once()) + ->method('process') + ->with($postQty) + ->willReturnArgument(0); + + $itemMock->expects($this->once()) + ->method('setQty') + ->with($postQty) + ->willReturnSelf(); + + $this->urlMock->expects($this->at(0)) + ->method('getUrl') + ->with('*/*', null) + ->willReturn($indexUrl); + + $itemMock->expects($this->once()) + ->method('getProductId') + ->willReturn($productId); + + $this->urlMock->expects($this->at(1)) + ->method('getUrl') + ->with('*/*/configure/', ['id' => $itemId, 'product_id' => $productId]) + ->willReturn($configureUrl); + + $optionMock = $this->getMockBuilder(\Magento\Wishlist\Model\Item\Option::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->optionFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($optionMock); + + $optionsMock = $this->getMockBuilder(\Magento\Wishlist\Model\ResourceModel\Item\Option\Collection::class) + ->disableOriginalConstructor() + ->getMock(); + $optionMock->expects($this->once()) + ->method('getCollection') + ->willReturn($optionsMock); + + $optionsMock->expects($this->once()) + ->method('addItemFilter') + ->with([$itemId]) + ->willReturnSelf(); + $optionsMock->expects($this->once()) + ->method('getOptionsByItem') + ->with($itemId) + ->willReturn($options); + + $itemMock->expects($this->once()) + ->method('setOptions') + ->with($options) + ->willReturnSelf(); + + $this->requestMock->expects($this->once()) + ->method('getParams') + ->willReturn($params); + + $buyRequestMock = $this->getMockBuilder(\Magento\Framework\DataObject::class) + ->disableOriginalConstructor() + ->getMock(); + + $itemMock->expects($this->once()) + ->method('getBuyRequest') + ->willReturn($buyRequestMock); + + $this->productHelperMock->expects($this->once()) + ->method('addParamsToBuyRequest') + ->with($params, ['current_config' => $buyRequestMock]) + ->willReturn($buyRequestMock); + + $itemMock->expects($this->once()) + ->method('mergeBuyRequest') + ->with($buyRequestMock) + ->willReturnSelf(); + $itemMock->expects($this->once()) + ->method('addToCart') + ->with($this->checkoutCartMock, true) + ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('message'))); + + $this->messageManagerMock->expects($this->once()) + ->method('addNotice') + ->with('message', null) + ->willReturnSelf(); + + $this->helperMock->expects($this->once()) + ->method('calculate') + ->willReturnSelf(); + + $this->resultRedirectMock->expects($this->once()) + ->method('setUrl') + ->with($configureUrl) + ->willReturnSelf(); + + $this->assertSame($this->resultRedirectMock, $this->model->execute()); + } } diff --git a/app/code/Magento/Wishlist/Test/Unit/Pricing/ConfiguredPrice/ConfigurableProductTest.php b/app/code/Magento/Wishlist/Test/Unit/Pricing/ConfiguredPrice/ConfigurableProductTest.php index 9e05a578080efbd7ec91f49babfbbafc447e330d..ee4841ef79f2e6e31545f12e44fe49073166a8e3 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Pricing/ConfiguredPrice/ConfigurableProductTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Pricing/ConfiguredPrice/ConfigurableProductTest.php @@ -5,36 +5,30 @@ */ namespace Magento\Wishlist\Test\Unit\Pricing\ConfiguredPrice; -use Magento\Framework\Pricing\Adjustment\CalculatorInterface; -use Magento\Framework\Pricing\PriceCurrencyInterface; -use Magento\Framework\Pricing\PriceInfoInterface; -use Magento\Framework\Pricing\SaleableInterface; -use Magento\Wishlist\Pricing\ConfiguredPrice\ConfigurableProduct; - class ConfigurableProductTest extends \PHPUnit_Framework_TestCase { /** - * @var SaleableInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\SaleableInterface|\PHPUnit_Framework_MockObject_MockObject */ private $saleableItem; /** - * @var CalculatorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\Adjustment\CalculatorInterface|\PHPUnit_Framework_MockObject_MockObject */ private $calculator; /** - * @var PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\PriceCurrencyInterface|\PHPUnit_Framework_MockObject_MockObject */ private $priceCurrency; /** - * @var ConfigurableProduct + * @var \Magento\Wishlist\Pricing\ConfiguredPrice\ConfigurableProduct */ private $model; /** - * @var PriceInfoInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Magento\Framework\Pricing\PriceInfoInterface|\PHPUnit_Framework_MockObject_MockObject */ private $priceInfoMock; @@ -49,9 +43,6 @@ class ConfigurableProductTest extends \PHPUnit_Framework_TestCase 'getCustomOption', ]) ->getMockForAbstractClass(); - $this->saleableItem->expects($this->once()) - ->method('getPriceInfo') - ->willReturn($this->priceInfoMock); $this->calculator = $this->getMockBuilder(\Magento\Framework\Pricing\Adjustment\CalculatorInterface::class) ->getMockForAbstractClass(); @@ -59,7 +50,7 @@ class ConfigurableProductTest extends \PHPUnit_Framework_TestCase $this->priceCurrency = $this->getMockBuilder(\Magento\Framework\Pricing\PriceCurrencyInterface::class) ->getMockForAbstractClass(); - $this->model = new ConfigurableProduct( + $this->model = new \Magento\Wishlist\Pricing\ConfiguredPrice\ConfigurableProduct( $this->saleableItem, null, $this->calculator, @@ -82,7 +73,7 @@ class ConfigurableProductTest extends \PHPUnit_Framework_TestCase ->getMock(); $this->priceInfoMock->expects($this->once()) ->method('getPrice') - ->with(ConfigurableProduct::PRICE_CODE) + ->with(\Magento\Wishlist\Pricing\ConfiguredPrice\ConfigurableProduct::PRICE_CODE) ->willReturn($priceMock); $productMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class) @@ -109,11 +100,28 @@ class ConfigurableProductTest extends \PHPUnit_Framework_TestCase public function testGetValueWithNoCustomOption() { + $priceValue = 100; + + $priceMock = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class) + ->getMockForAbstractClass(); + $priceMock->expects($this->once()) + ->method('getValue') + ->willReturn($priceValue); + $this->saleableItem->expects($this->once()) ->method('getCustomOption') ->with('simple_product') ->willReturn(null); - $this->assertEquals(0, $this->model->getValue()); + $this->saleableItem->expects($this->once()) + ->method('getPriceInfo') + ->willReturn($this->priceInfoMock); + + $this->priceInfoMock->expects($this->once()) + ->method('getPrice') + ->with(\Magento\Wishlist\Pricing\ConfiguredPrice\ConfigurableProduct::PRICE_CODE) + ->willReturn($priceMock); + + $this->assertEquals(100, $this->model->getValue()); } } diff --git a/app/code/Magento/Wishlist/composer.json b/app/code/Magento/Wishlist/composer.json index 3faee8912493c96092f70d7fc3ca0e77164b39bc..b341edde760b0761872aecc488216faa37392c22 100644 --- a/app/code/Magento/Wishlist/composer.json +++ b/app/code/Magento/Wishlist/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-wishlist", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/module-store": "100.2.*", "magento/module-customer": "100.2.*", "magento/module-catalog": "101.1.*", diff --git a/app/code/Magento/Wishlist/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Wishlist/view/frontend/layout/catalog_product_view.xml index 4c01d341bb68294cae5d48f5ab2dd99d8d5b08d8..a25546b1afc902a563c296a7adc24b1a9787729b 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/catalog_product_view.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/catalog_product_view.xml @@ -16,7 +16,7 @@ </arguments> </block> <referenceBlock name="product.info.addto"> - <block class="Magento\Wishlist\Block\Catalog\Product\View\AddTo\Wishlist" name="view.addto.wishlist" after="view.addto.requisition" + <block class="Magento\Wishlist\Block\Catalog\Product\View\AddTo\Wishlist" name="view.addto.wishlist" template="Magento_Wishlist::catalog/product/view/addto/wishlist.phtml" /> </referenceBlock> </referenceContainer> diff --git a/app/code/Magento/Wishlist/view/frontend/layout/customer_account.xml b/app/code/Magento/Wishlist/view/frontend/layout/customer_account.xml index c1dc653efbe7e2f8877a926073f55744a744a624..9083fcdc8d13280dbb16fb917bc24a2bc577ad2d 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/customer_account.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/customer_account.xml @@ -8,10 +8,11 @@ <page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd"> <body> <referenceBlock name="customer_account_navigation"> - <block class="Magento\Framework\View\Element\Html\Link\Current" ifconfig="wishlist/general/active" name="customer-account-navigation-wish-list-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" ifconfig="wishlist/general/active" name="customer-account-navigation-wish-list-link"> <arguments> <argument name="path" xsi:type="string">wishlist</argument> <argument name="label" xsi:type="string">My Wish List</argument> + <argument name="sortOrder" xsi:type="number">210</argument> </arguments> </block> </referenceBlock> diff --git a/app/code/Magento/Wishlist/view/frontend/layout/default.xml b/app/code/Magento/Wishlist/view/frontend/layout/default.xml index a0fc577547c5ac8ea91bc0b9699edcc6a2d499b3..dffa4ec79d64d3183e732aa043ccefc39fa1062f 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/default.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/default.xml @@ -11,7 +11,11 @@ <block class="Magento\Framework\View\Element\Js\Components" name="wishlist_page_head_components" template="Magento_Wishlist::js/components.phtml"/> </referenceBlock> <referenceBlock name="top.links"> - <block class="Magento\Wishlist\Block\Link" name="wish-list-link" after="my-account-link"/> + <block class="Magento\Wishlist\Block\Link" name="wish-list-link" after="my-account-link"> + <arguments> + <argument name="sortOrder" xsi:type="number">60</argument> + </arguments> + </block> </referenceBlock> <referenceContainer name="sidebar.additional"> <block class="Magento\Wishlist\Block\Customer\Sidebar" name="wishlist_sidebar" as="wishlist" template="Magento_Wishlist::sidebar.phtml"/> diff --git a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_configure.xml b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_configure.xml index 50ba68940fc9d4027b359fe72ec86b305fb0f69a..02b737b7d5127320207669379ea7e3b09ddb5ac1 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_configure.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_configure.xml @@ -9,7 +9,7 @@ <update handle="catalog_product_view"/> <body> <referenceBlock name="product.info.addto"> - <block class="Magento\Wishlist\Block\Item\Configure" name="view.addto.wishlist" after="view.addto.requisition" + <block class="Magento\Wishlist\Block\Item\Configure" name="view.addto.wishlist" template="item/configure/addto/wishlist.phtml" /> </referenceBlock> </body> diff --git a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_configure_type_bundle.xml b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_configure_type_bundle.xml index dbb680f8f25806508e44ef31724899149ca2b6ce..84d0429a29c99ba204ec9d4401b69d2bf68e1abf 100644 --- a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_configure_type_bundle.xml +++ b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_configure_type_bundle.xml @@ -20,9 +20,9 @@ <block class="Magento\Catalog\Block\Product\View" name="product.info.addtocart.bundle" as="addtocart" template="product/view/addtocart.phtml" /> <block class="Magento\Catalog\Block\Product\View" name="product.info.addto.bundle" as="addto" after="product.info.addtocart.bundle" template="Magento_Catalog::product/view/addto.phtml" cacheable="false"> - <block class="Magento\Wishlist\Block\Item\Configure" name="view.addto.wishlist.bundle" after="view.addto.requisition" + <block class="Magento\Wishlist\Block\Item\Configure" name="view.addto.wishlist.bundle" template="item/configure/addto/wishlist.phtml" /> - <block class="Magento\Catalog\Block\Product\View\Addto\Compare" name="view.addto.compare.bundle" after="view.addto.wishlist" + <block class="Magento\Catalog\Block\Product\View\AddTo\Compare" name="view.addto.compare.bundle" after="view.addto.wishlist" template="Magento_Catalog::product/view/addto/compare.phtml" /> </block> </block> diff --git a/app/design/adminhtml/Magento/backend/composer.json b/app/design/adminhtml/Magento/backend/composer.json index 29569db622d2ccc9fad9e9b87b3ad4f7ae4757d2..ff0eda055bba6a9e9899d66839216c14bbdbe02e 100644 --- a/app/design/adminhtml/Magento/backend/composer.json +++ b/app/design/adminhtml/Magento/backend/composer.json @@ -2,7 +2,7 @@ "name": "magento/theme-adminhtml-backend", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*" }, "type": "magento2-theme", diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less index 7731595870f061c990acc5cc36d68980e8ba590f..453a9ce958f4d3f2716c015c99ff72a525b3838f 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less @@ -24,6 +24,7 @@ & when (@media-common = true) { .checkout-payment-method { .step-title { + border-bottom: 0; margin-bottom: 0; } @@ -39,6 +40,12 @@ display: block; } } + + & + .payment-method { + .payment-method-title { + .lib-css(border-top, @checkout-payment-method-title__border); + } + } } .payment-method-content { @@ -54,7 +61,6 @@ } .payment-method-title { - .lib-css(border-top, @checkout-payment-method-title__border); .lib-css(padding, @checkout-payment-method-title__padding 0); margin: 0; @@ -80,6 +86,14 @@ } } + .payment-group { + & + .payment-group { + .step-title { + margin: @indent__base 0 0; + } + } + } + .field-select-billing, .billing-address-form { .lib-css(max-width, @checkout-billing-address-form__max-width); diff --git a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less index 369e100506c4d76dd10347a6ee5ee772d605091f..2d54baf8f8e9f74bd3d764133bcc5a58e30ea2e0 100644 --- a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less +++ b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less @@ -15,6 +15,8 @@ @account-nav-current-color: false; @account-nav-current-font-weight: @font-weight__semibold; +@account-nav-delimiter__border-color: @color-gray82; + @account-nav-item-hover: @color-gray91; @_password-default: @color-gray-light01; @@ -219,6 +221,12 @@ .lib-css(border-color, @account-nav-current-border-color); } } + + .delimiter { + border-top: 1px solid @account-nav-delimiter__border-color; + display: block; + margin: @indent__s 1.8rem; + } } } diff --git a/app/design/frontend/Magento/blank/composer.json b/app/design/frontend/Magento/blank/composer.json index 27ed40860dd5b065dc51e82f487722eb519ba215..7c03167ebc23278eaab5a72cddd381de7cd5afcc 100644 --- a/app/design/frontend/Magento/blank/composer.json +++ b/app/design/frontend/Magento/blank/composer.json @@ -2,7 +2,7 @@ "name": "magento/theme-frontend-blank", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.2.*" }, "type": "magento2-theme", diff --git a/app/design/frontend/Magento/blank/etc/view.xml b/app/design/frontend/Magento/blank/etc/view.xml index 3a92ad0a6741040e0adb3fc25e00948520cfa565..6c53a8613f09ca1f91c9e024b8e2cec723f0b309 100644 --- a/app/design/frontend/Magento/blank/etc/view.xml +++ b/app/design/frontend/Magento/blank/etc/view.xml @@ -227,7 +227,7 @@ </var> <var name="options"> <var name="options"> - <var name="navigation">dots</var> + <var name="nav">dots</var> </var> </var> </var> diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less index 85b2e96e6ea4add2316a572f0ce4ad16bfe073d2..a8c84ef51a92af04d403be3eb522610693a9a823 100644 --- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less +++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less @@ -24,6 +24,7 @@ & when (@media-common = true) { .checkout-payment-method { .step-title { + border-bottom: 0; margin-bottom: 0; } @@ -39,6 +40,12 @@ display: block; } } + + & + .payment-method { + .payment-method-title { + .lib-css(border-top, @checkout-payment-method-title__border); + } + } } .payment-method-content { @@ -54,7 +61,6 @@ } .payment-method-title { - .lib-css(border-top, @checkout-payment-method-title__border); .lib-css(padding, @checkout-payment-method-title__padding 0); margin: 0; @@ -80,6 +86,14 @@ } } + .payment-group { + & + .payment-group { + .step-title { + margin: @indent__base 0 0; + } + } + } + .field-select-billing, .billing-address-form { .lib-css(max-width, @checkout-billing-address-form__max-width); diff --git a/app/design/frontend/Magento/luma/Magento_Customer/layout/customer_account.xml b/app/design/frontend/Magento/luma/Magento_Customer/layout/customer_account.xml index 5d02aec14bedc148d2cade4f47b87e496da23b9f..be8aa1200f21b0a861c088666f62828e052a1f7c 100644 --- a/app/design/frontend/Magento/luma/Magento_Customer/layout/customer_account.xml +++ b/app/design/frontend/Magento/luma/Magento_Customer/layout/customer_account.xml @@ -13,26 +13,41 @@ <argument name="block_title" translate="true" xsi:type="string">Account Dashboard</argument> <argument name="block_css" xsi:type="string">block-collapsible-nav</argument> </arguments> - <block class="Magento\Framework\View\Element\Html\Links" name="customer_account_navigation" before="-"> + <block class="Magento\Customer\Block\Account\Navigation" name="customer_account_navigation" before="-"> <arguments> <argument name="css_class" xsi:type="string">nav items</argument> </arguments> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-account-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-account-link"> <arguments> <argument name="label" xsi:type="string" translate="true">Account Dashboard</argument> <argument name="path" xsi:type="string">customer/account</argument> + <argument name="sortOrder" xsi:type="number">250</argument> </arguments> </block> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-account-edit-link"> + <block class="Magento\Customer\Block\Account\Delimiter" name="customer-account-navigation-delimiter-1" + template="Magento_Customer::account/navigation-delimiter.phtml"> <arguments> - <argument name="label" xsi:type="string" translate="true">Account Information</argument> - <argument name="path" xsi:type="string">customer/account/edit</argument> + <argument name="sortOrder" xsi:type="number">200</argument> </arguments> </block> - <block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-address-link"> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-address-link"> <arguments> <argument name="label" xsi:type="string" translate="true">Address Book</argument> <argument name="path" xsi:type="string">customer/address</argument> + <argument name="sortOrder" xsi:type="number">190</argument> + </arguments> + </block> + <block class="Magento\Customer\Block\Account\SortLinkInterface" name="customer-account-navigation-account-edit-link"> + <arguments> + <argument name="label" xsi:type="string" translate="true">Account Information</argument> + <argument name="path" xsi:type="string">customer/account/edit</argument> + <argument name="sortOrder" xsi:type="number">180</argument> + </arguments> + </block> + <block class="Magento\Customer\Block\Account\Delimiter" name="customer-account-navigation-delimiter-2" + template="Magento_Customer::account/navigation-delimiter.phtml"> + <arguments> + <argument name="sortOrder" xsi:type="number">130</argument> </arguments> </block> </block> diff --git a/app/design/frontend/Magento/luma/Magento_Customer/layout/default.xml b/app/design/frontend/Magento/luma/Magento_Customer/layout/default.xml index bd1fd91d6d338bd743ae49db05440c38f22e94a9..2592f9def7fa42f0d8e69cf0458d568651843f26 100644 --- a/app/design/frontend/Magento/luma/Magento_Customer/layout/default.xml +++ b/app/design/frontend/Magento/luma/Magento_Customer/layout/default.xml @@ -9,7 +9,11 @@ <body> <referenceBlock name="header.links"> <block class="Magento\Customer\Block\Account\Customer" name="customer" template="account/customer.phtml" before="-"/> - <block class="Magento\Customer\Block\Account\AuthorizationLink" name="authorization-link-login" template="account/link/authorization.phtml"/> + <block class="Magento\Customer\Block\Account\AuthorizationLink" name="authorization-link-login" template="account/link/authorization.phtml"> + <arguments> + <argument name="sortOrder" xsi:type="number">10</argument> + </arguments> + </block> </referenceBlock> <block class="Magento\Theme\Block\Html\Header" name="header" as="header"> <arguments> diff --git a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/module/_collapsible_navigation.less b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/module/_collapsible_navigation.less index 95c7ec15ebb33a90e81b03ab553c636f39033fb1..33c0dacb8d99d8e3bb031114a43fd10dc3e767b1 100644 --- a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/module/_collapsible_navigation.less +++ b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/module/_collapsible_navigation.less @@ -13,6 +13,7 @@ @collapsible-nav-current-border-color: @color-orange-red1; @collapsible-nav-current-color: @color-black; @collapsible-nav-current-font-weight: @font-weight__semibold; +@collapsible-nav-delimiter__border-color: @color-gray82; @collapsible-nav-item-hover: @color-gray91; // @@ -64,6 +65,12 @@ .lib-css(border-color, @collapsible-nav-current-border-color); } } + + .delimiter { + border-top: 1px solid @collapsible-nav-delimiter__border-color; + display: block; + margin: @indent__s 1.8rem; + } } } } diff --git a/app/design/frontend/Magento/luma/composer.json b/app/design/frontend/Magento/luma/composer.json index 98fe0ebf8061c23fa37d30a9551f05d5c41bce1b..97eb48f3c7e9efe028eae034757a6a17c4d98bd0 100644 --- a/app/design/frontend/Magento/luma/composer.json +++ b/app/design/frontend/Magento/luma/composer.json @@ -2,7 +2,7 @@ "name": "magento/theme-frontend-luma", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/theme-frontend-blank": "100.2.*", "magento/framework": "100.2.*" }, diff --git a/composer.json b/composer.json index fed4465140724cef3c24cd7facdd2ec5ceeba5b6..f9e49415d6ad3aff6976144df442861909155d2d 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "AFL-3.0" ], "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "zendframework/zend-stdlib": "~2.4.6", "zendframework/zend-code": "~2.4.6", "zendframework/zend-server": "~2.4.6", diff --git a/composer.lock b/composer.lock index 7feb568d766d012e009c59a50226f3ea8bd7b74a..02f0e31a325638840641667cac1c6783cf381e87 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "c23e80be1cc71ab108ce5ac19b3fe509", - "content-hash": "5b9734c1bdbda68cf20507525cafa0f2", + "hash": "1f34dce6d48c9e4e694c27e001414000", + "content-hash": "600aca1692cf3fe5c2ea1cbf66de09ab", "packages": [ { "name": "braintree/braintree_php", @@ -56,7 +56,7 @@ }, { "name": "colinmollenhour/cache-backend-file", - "version": "1.4", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/colinmollenhour/Cm_Cache_Backend_File.git", @@ -818,16 +818,16 @@ }, { "name": "paragonie/random_compat", - "version": "v2.0.2", + "version": "v2.0.3", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf" + "reference": "c0125896dbb151380ab47e96c621741e79623beb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/088c04e2f261c33bed6ca5245491cfca69195ccf", - "reference": "088c04e2f261c33bed6ca5245491cfca69195ccf", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/c0125896dbb151380ab47e96c621741e79623beb", + "reference": "c0125896dbb151380ab47e96c621741e79623beb", "shasum": "" }, "require": { @@ -862,7 +862,7 @@ "pseudorandom", "random" ], - "time": "2016-04-03 06:00:07" + "time": "2016-10-17 15:23:22" }, { "name": "pelago/emogrifier", @@ -922,16 +922,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "41f85e9c2582b3f6d1b7d20395fb40c687ad5370" + "reference": "ab8028c93c03cc8d9c824efa75dc94f1db2369bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/41f85e9c2582b3f6d1b7d20395fb40c687ad5370", - "reference": "41f85e9c2582b3f6d1b7d20395fb40c687ad5370", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/ab8028c93c03cc8d9c824efa75dc94f1db2369bf", + "reference": "ab8028c93c03cc8d9c824efa75dc94f1db2369bf", "shasum": "" }, "require": { @@ -1010,20 +1010,20 @@ "x.509", "x509" ], - "time": "2016-08-18 18:49:14" + "time": "2016-10-04 00:57:04" }, { "name": "psr/log", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "5277094ed527a1c4477177d102fe4c53551953e0" + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/5277094ed527a1c4477177d102fe4c53551953e0", - "reference": "5277094ed527a1c4477177d102fe4c53551953e0", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", "shasum": "" }, "require": { @@ -1057,7 +1057,7 @@ "psr", "psr-3" ], - "time": "2016-09-19 16:02:08" + "time": "2016-10-10 12:19:37" }, { "name": "ramsey/uuid", @@ -1390,7 +1390,7 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.11", + "version": "v2.8.12", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", @@ -1450,7 +1450,7 @@ }, { "name": "symfony/filesystem", - "version": "v2.8.11", + "version": "v2.8.12", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -1499,16 +1499,16 @@ }, { "name": "symfony/finder", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577" + "reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/e568ef1784f447a0e54dcb6f6de30b9747b0f577", - "reference": "e568ef1784f447a0e54dcb6f6de30b9747b0f577", + "url": "https://api.github.com/repos/symfony/finder/zipball/205b5ffbb518a98ba2ae60a52656c4a31ab00c6f", + "reference": "205b5ffbb518a98ba2ae60a52656c4a31ab00c6f", "shasum": "" }, "require": { @@ -1544,20 +1544,20 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2016-08-26 12:04:02" + "time": "2016-09-28 00:11:12" }, { "name": "symfony/process", - "version": "v2.8.11", + "version": "v2.8.12", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "05a03ed27073638658cab9405d99a67dd1014987" + "reference": "024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/05a03ed27073638658cab9405d99a67dd1014987", - "reference": "05a03ed27073638658cab9405d99a67dd1014987", + "url": "https://api.github.com/repos/symfony/process/zipball/024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f", + "reference": "024de37f8a6b9e5e8244d9eb3fcf3e467dd2a93f", "shasum": "" }, "require": { @@ -1593,7 +1593,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2016-09-06 10:55:00" + "time": "2016-09-29 14:03:54" }, { "name": "tedivm/jshrink", @@ -4329,16 +4329,16 @@ }, { "name": "symfony/config", - "version": "v2.8.11", + "version": "v2.8.12", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "005bf10c156335ede2e89fb9a9ee10a0b742bc84" + "reference": "f8b1922bbda9d2ac86aecd649399040bce849fde" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/005bf10c156335ede2e89fb9a9ee10a0b742bc84", - "reference": "005bf10c156335ede2e89fb9a9ee10a0b742bc84", + "url": "https://api.github.com/repos/symfony/config/zipball/f8b1922bbda9d2ac86aecd649399040bce849fde", + "reference": "f8b1922bbda9d2ac86aecd649399040bce849fde", "shasum": "" }, "require": { @@ -4378,20 +4378,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2016-08-16 14:56:08" + "time": "2016-09-14 20:31:12" }, { "name": "symfony/dependency-injection", - "version": "v2.8.11", + "version": "v2.8.12", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "0a732a9cafc30e54077967da4d019e1d618a8cb9" + "reference": "ee9ec9ac2b046462d341e9de7c4346142d335e75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/0a732a9cafc30e54077967da4d019e1d618a8cb9", - "reference": "0a732a9cafc30e54077967da4d019e1d618a8cb9", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/ee9ec9ac2b046462d341e9de7c4346142d335e75", + "reference": "ee9ec9ac2b046462d341e9de7c4346142d335e75", "shasum": "" }, "require": { @@ -4441,11 +4441,11 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", - "time": "2016-09-06 23:19:39" + "time": "2016-09-24 09:47:20" }, { "name": "symfony/stopwatch", - "version": "v3.1.4", + "version": "v3.1.5", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", @@ -4494,7 +4494,7 @@ }, { "name": "symfony/yaml", - "version": "v2.8.11", + "version": "v2.8.12", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", @@ -4590,7 +4590,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "lib-libxml": "*", "ext-ctype": "*", "ext-gd": "*", diff --git a/dev/tests/api-functional/_files/Magento/TestModuleIntegrationFromConfig/composer.json b/dev/tests/api-functional/_files/Magento/TestModuleIntegrationFromConfig/composer.json index 629afd442a46e7549206c1520b1b6c75f024dcb1..00888c7baf74990c6ba4d98ba6569c210ba08c7e 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleIntegrationFromConfig/composer.json +++ b/dev/tests/api-functional/_files/Magento/TestModuleIntegrationFromConfig/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-test-module-integration-from-config", "description": "test integration create from config", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "0.42.0-beta8", "magento/module-integration": "0.42.0-beta8" }, @@ -16,4 +16,4 @@ ] ] } -} \ No newline at end of file +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/composer.json b/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/composer.json index 1e2d9c0fa13dd80fcfe69374a7e02700f6b570ea..a9aa5a2ede89ce314a1f2257bc70c6b20b3bb2a0 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/composer.json +++ b/dev/tests/api-functional/_files/Magento/TestModuleJoinDirectives/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-test-join-directives", "description": "test integration for join directives", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "0.42.0-beta8", "magento/module-sales": "0.42.0-beta8" }, 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 4191ec1faa959aa1203ee45850c298f1d43bb988..cd02c6d4d7500ef9adf7d17ac9f866bfc8ef0711 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/ProductRepositoryInterfaceTest.php @@ -713,6 +713,18 @@ class ProductRepositoryInterfaceTest extends WebapiAbstract $this->assertNotNull($response['items'][0]['sku']); $this->assertEquals('simple', $response['items'][0]['sku']); + + $index = null; + foreach ($response['items'][0]['custom_attributes'] as $key => $customAttribute) { + if ($customAttribute['attribute_code'] == 'category_ids') { + $index = $key; + break; + } + } + $this->assertNotNull($index, 'Category information wasn\'t set'); + + $expectedResult = (TESTS_WEB_API_ADAPTER == self::ADAPTER_SOAP) ? ['string' => '2'] : ['2']; + $this->assertEquals($expectedResult, $response['items'][0]['custom_attributes'][$index]['value']); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/LinkManagementTest.php b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/LinkManagementTest.php index e4129a0739912c05dbb8546beab7f028945ad5f6..1a06be978d34c897f10aadd32e7f3e1720b1983b 100644 --- a/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/LinkManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/ConfigurableProduct/Api/LinkManagementTest.php @@ -6,12 +6,33 @@ */ namespace Magento\ConfigurableProduct\Api; +use Magento\Eav\Model\AttributeRepository; + class LinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract { const SERVICE_NAME = 'configurableProductLinkManagementV1'; const SERVICE_VERSION = 'V1'; const RESOURCE_PATH = '/V1/configurable-products'; + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + protected $objectManager; + + /** + * @var AttributeRepository + */ + protected $attributeRepository; + + /** + * Execute per test initialization + */ + public function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->attributeRepository = $this->objectManager->get(\Magento\Eav\Model\AttributeRepository::class); + } + /** * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php */ @@ -40,14 +61,76 @@ class LinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract } /** - * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_simple_77.php * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_simple_77.php * @magentoApiDataFixture Magento/ConfigurableProduct/_files/delete_association.php */ public function testAddChild() { $productSku = 'configurable'; $childSku = 'simple_77'; + $res = $this->addChild($productSku, $childSku); + $this->assertTrue($res); + } + + /** + * Test the full flow of creating a configurable product and adding a child via REST + * + * @magentoApiDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php + */ + public function testAddChildFullRestCreation() + { + $productSku = 'configurable-product-sku'; + $childSku = 'simple-product-sku'; + + $this->createConfigurableProduct($productSku); + $attribute = $this->attributeRepository->get('catalog_product', 'test_configurable'); + $attributeValue = $attribute->getOptions()[1]->getValue(); + $this->addOptionToConfigurableProduct($productSku, $attribute->getAttributeId(), $attributeValue); + $this->createSimpleProduct($childSku, $attributeValue); + $res = $this->addChild($productSku, $childSku); + $this->assertTrue($res); + + // confirm that the simple product was added + $children = $this->getChildren($productSku); + $added = false; + foreach ($children as $child) { + if ($child['sku'] == $childSku) { + $added = true; + break; + } + } + $this->assertTrue($added); + + // clean up products + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/products/' . $productSku, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE + ], + 'soap' => [ + 'service' => 'catalogProductRepositoryV1', + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => 'catalogProductRepositoryV1DeleteById', + ], + ]; + $this->_webApiCall($serviceInfo, ['sku' => $productSku]); + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/products/' . $childSku, + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_DELETE + ], + 'soap' => [ + 'service' => 'catalogProductRepositoryV1', + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => 'catalogProductRepositoryV1DeleteById', + ], + ]; + $this->_webApiCall($serviceInfo, ['sku' => $childSku]); + } + + private function addChild($productSku, $childSku) + { $serviceInfo = [ 'rest' => [ 'resourcePath' => self::RESOURCE_PATH . '/' . $productSku . '/child', @@ -59,8 +142,90 @@ class LinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract 'operation' => self::SERVICE_NAME . 'AddChild' ] ]; - $res = $this->_webApiCall($serviceInfo, ['sku' => $productSku, 'childSku' => $childSku]); - $this->assertTrue($res); + return $this->_webApiCall($serviceInfo, ['sku' => $productSku, 'childSku' => $childSku]); + } + + protected function createConfigurableProduct($productSku) + { + $requestData = [ + 'product' => [ + 'sku' => $productSku, + 'name' => 'configurable-product-' . $productSku, + 'type_id' => 'configurable', + 'price' => 50, + 'attribute_set_id' => 4 + ] + ]; + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/products', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST + ], + 'soap' => [ + 'service' => 'catalogProductRepositoryV1', + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => 'catalogProductRepositoryV1Save', + ], + ]; + return $this->_webApiCall($serviceInfo, $requestData); + } + + protected function addOptionToConfigurableProduct($productSku, $attributeId, $attributeValue) + { + $requestData = [ + 'sku' => $productSku, + 'option' => [ + 'attribute_id' => $attributeId, + 'label' => 'test_configurable', + 'position' => 0, + 'is_use_default' => true, + 'values' => [ + ['value_index' => $attributeValue], + ] + ] + ]; + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/configurable-products/'. $productSku .'/options', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => 'configurableProductOptionRepositoryV1', + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => 'configurableProductOptionRepositoryV1Save', + ], + ]; + return $this->_webApiCall($serviceInfo, $requestData); + } + + protected function createSimpleProduct($sku, $attributeValue) + { + $requestData = [ + 'product' => [ + 'sku' => $sku, + 'name' => 'simple-product-' . $sku, + 'type_id' => 'simple', + 'attribute_set_id' => 4, + 'price' => 3.62, + 'status' => 1, + 'visibility' => 4, + 'custom_attributes' => [ + ['attribute_code' => 'test_configurable', 'value' => $attributeValue], + ] + ] + ]; + $serviceInfo = [ + 'rest' => [ + 'resourcePath' => '/V1/products', + 'httpMethod' => \Magento\Framework\Webapi\Rest\Request::HTTP_METHOD_POST, + ], + 'soap' => [ + 'service' => 'catalogProductRepositoryV1', + 'serviceVersion' => self::SERVICE_VERSION, + 'operation' => 'catalogProductRepositoryV1Save', + ], + ]; + return $this->_webApiCall($serviceInfo, $requestData); } /** @@ -93,7 +258,7 @@ class LinkManagementTest extends \Magento\TestFramework\TestCase\WebapiAbstract /** * @param string $productSku - * @return string + * @return string[] */ protected function getChildren($productSku) { diff --git a/dev/tests/functional/composer.json b/dev/tests/functional/composer.json index 512c5bc8d5df666ced655806af0da64952d0f4ef..cef145167860c8ebad9695d870b6cc73580e47f3 100644 --- a/dev/tests/functional/composer.json +++ b/dev/tests/functional/composer.json @@ -1,7 +1,7 @@ { "require": { "magento/mtf": "1.0.0-rc48", - "php": "~5.6.0|7.0.2|~7.0.6", + "php": "~5.6.5|7.0.2|~7.0.6", "phpunit/phpunit": "~4.8.0|~5.5.0", "phpunit/phpunit-selenium": ">=1.2" }, diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Template.php b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Template.php index 29361ced38bcc6b4ba344e2191f3c963eb92576d..fb22754faf483aa32b97d3e1672aeb17d58bec26 100644 --- a/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Template.php +++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Block/Template.php @@ -19,7 +19,7 @@ class Template extends Block * * @var string */ - protected $spinner = '[data-role="spinner"]'; + protected $spinner = '#container [data-role="spinner"]'; /** * Magento loader. diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderWithPayPalBraintreeVaultBackendTest.php b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderWithPayPalBraintreeVaultBackendTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6fde39412d1fe66a987b12cf5cb29e09d81327fe --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderWithPayPalBraintreeVaultBackendTest.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Test\TestCase; + +use Magento\Mtf\TestCase\Scenario; + +/** + * Preconditions: + * 1. Configure shipping method. + * 2. Configure payment method. + * 3. Create products. + * 4. Create and setup customer. + * + * Steps: + * 1. Log in Storefront. + * 2. Add products to the Shopping Cart. + * 3. In 'Estimate Shipping and Tax' section specify destination using values from Test Data. + * 4. Click the 'Proceed to Checkout' button. + * 5. Fill shipping information. + * 6. Select shipping method. + * 8. Select payment method + * 9. Verify order total on review step. + * 10. Click 'Continue to PayPal' button. + * 11. Click 'Proceed purchase' in popup. + * 12. Log in Admin panel. + * 13. Open placed order. + * 14. Click 'Reorder' button. + * 15. Select stored Braintree PayPal token. + * 16. Click 'Submit Order'. + * 17. Perform assertions. + * + * @group Braintree + * @ZephyrId MAGETWO-59259 + */ +class CreateOrderWithPayPalBraintreeVaultBackendTest extends Scenario +{ + /* tags */ + const MVP = 'yes'; + + const TEST_TYPE = '3rd_party_test'; + + const SEVERITY = 'S0'; + /* end tags */ + + /** + * Runs test scenario + * @return void + */ + public function test() + { + $this->executeScenario(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderWithPayPalBraintreeVaultBackendTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderWithPayPalBraintreeVaultBackendTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..03e0ba330fa3b74e2a33c2983655ced95cf3dbfe --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateOrderWithPayPalBraintreeVaultBackendTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Braintree\Test\TestCase\CreateOrderWithPayPalBraintreeVaultBackendTest" summary="Checkout with PayPal Braintree Vault token from Admin"> + <variation name="CreateOrderWithPayPalBraintreeVaultBackendTestVariation1" summary="Checkout with PayPal Braintree Vault token from Admin" ticketId="MAGETWO-59259"> + <data name="tag" xsi:type="string">est_type:3rd_party_test, severity:S0</data> + <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> + <data name="customer/dataset" xsi:type="string">default</data> + <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data> + <data name="checkoutMethod" xsi:type="string">login</data> + <data name="shipping/shipping_service" xsi:type="string">Flat Rate</data> + <data name="shipping/shipping_method" xsi:type="string">Fixed</data> + <data name="payment/method" xsi:type="string">braintree_paypal</data> + <data name="vault/method" xsi:type="string">braintree_paypal_vault</data> + <data name="status" xsi:type="string">Processing</data> + <data name="configData" xsi:type="string">braintree, braintree_paypal, braintree_paypal_use_vault, braintree_paypal_skip_order_review</data> + <data name="prices" xsi:type="array"> + <item name="grandTotal" xsi:type="string">15.00</item> + </data> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderSuccessCreateMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> + <constraint name="Magento\Sales\Test\Constraint\AssertAuthorizationInCommentsHistory" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGrid" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderInOrdersGridOnFrontend" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateVaultOrderBackendTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateVaultOrderBackendTest.xml index db6cd3572eaa2acec5ded94997b1951c744e6652..c9b26df050cfc9cf9ef4913a4f538459cee1923f 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateVaultOrderBackendTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/CreateVaultOrderBackendTest.xml @@ -19,6 +19,7 @@ <item name="grandTotal" xsi:type="string">15.00</item> </data> <data name="payment/method" xsi:type="string">braintree</data> + <data name="vault/method" xsi:type="string">braintree_cc_vault</data> <data name="creditCardClass" xsi:type="string">credit_card_braintree</data> <data name="creditCard/dataset" xsi:type="string">visa_braintree</data> <data name="creditCardSave" xsi:type="string">Yes</data> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithDiscountTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithDiscountTest.xml index 23fc729edb99f9645f7587d8e350464c81083fcd..f8ffa51982f1fae4d34a6290e1bebce28ba85712 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithDiscountTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/OnePageCheckoutWithDiscountTest.xml @@ -30,5 +30,25 @@ <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> </variation> + <variation name="OnePageCheckoutWithDiscountTestVariation2" summary="Checkout with 100% discount and free shipping if Braintree through PayPal is enabled" ticketId="MAGETWO-59940"> + <data name="description" xsi:type="string">Use saved for Braintree credit card on checkout</data> + <data name="products/0" xsi:type="string">catalogProductSimple::product_10_dollar</data> + <data name="customer/dataset" xsi:type="string">default</data> + <data name="salesRule" xsi:type="string">active_sales_rule_with_fixed_price_discount_coupon</data> + <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data> + <data name="checkoutMethod" xsi:type="string">login</data> + <data name="shipping/shipping_service" xsi:type="string">Free Shipping</data> + <data name="shipping/shipping_method" xsi:type="string">Free</data> + <data name="payment/method" xsi:type="string">free</data> + <data name="prices" xsi:type="array"> + <item name="grandTotal" xsi:type="string">0.00</item> + </data> + <data name="configData" xsi:type="string">braintree, braintree_use_vault, freeshipping</data> + <data name="status" xsi:type="string">Pending</data> + <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S1</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderGrandTotal" /> + <constraint name="Magento\Sales\Test\Constraint\AssertOrderStatusIsCorrect" /> + </variation> </testCase> </config> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/ReorderUsingVaultTest.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/ReorderUsingVaultTest.xml index 5a99c4a89a313392cdd596891712b1d2d4bbaec3..ad4d5cc06e92e260aed4c90e6f534f2540d25034 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/ReorderUsingVaultTest.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/TestCase/ReorderUsingVaultTest.xml @@ -19,6 +19,7 @@ <item name="grandTotal" xsi:type="string">15.00</item> </data> <data name="payment/method" xsi:type="string">braintree</data> + <data name="vault/method" xsi:type="string">braintree_cc_vault</data> <data name="creditCardClass" xsi:type="string">credit_card_braintree</data> <data name="creditCard/dataset" xsi:type="string">visa_braintree</data> <data name="configData" xsi:type="string">braintree, braintree_use_vault</data> diff --git a/dev/tests/functional/tests/app/Magento/Braintree/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Braintree/Test/etc/testcase.xml index 34f4be6493838598b9872225997736da38b07ff7..495f455c43a72f4d61029a07a8633fc09744c2f0 100644 --- a/dev/tests/functional/tests/app/Magento/Braintree/Test/etc/testcase.xml +++ b/dev/tests/functional/tests/app/Magento/Braintree/Test/etc/testcase.xml @@ -158,4 +158,21 @@ <step name="changeOrderStatusToPaymentReview" module="Magento_Braintree" next="denyPayment" /> <step name="denyPayment" module="Magento_Braintree" /> </scenario> + <scenario name="CreateOrderWithPayPalBraintreeVaultBackendTest" firstStep="setupConfiguration"> + <step name="setupConfiguration" module="Magento_Config" next="createProducts" /> + <step name="createProducts" module="Magento_Catalog" next="addProductsToTheCart" /> + <step name="addProductsToTheCart" module="Magento_Checkout" next="estimateShippingAndTax" /> + <step name="estimateShippingAndTax" module="Magento_Checkout" next="clickProceedToCheckout" /> + <step name="clickProceedToCheckout" module="Magento_Checkout" next="createCustomer" /> + <step name="createCustomer" module="Magento_Customer" next="selectCheckoutMethod" /> + <step name="selectCheckoutMethod" module="Magento_Checkout" next="fillShippingAddress" /> + <step name="fillShippingAddress" module="Magento_Checkout" next="fillShippingMethod" /> + <step name="fillShippingMethod" module="Magento_Checkout" next="selectPaymentMethod" /> + <step name="selectPaymentMethod" module="Magento_Checkout" next="placeOrderWithPaypal" /> + <step name="placeOrderWithPaypal" module="Magento_Braintree" next="openOrder"/> + <step name="openOrder" module="Magento_Sales" next="reorder" /> + <step name="reorder" module="Magento_Sales" next="useVaultPaymentToken" /> + <step name="useVaultPaymentToken" module="Magento_Vault" next="submitOrder" /> + <step name="submitOrder" module="Magento_Sales" /> + </scenario> </config> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Bundle.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Bundle.php index e94ec38d010f3d519e8ab2e06c5fdd21dccd544c..5cf84cf6764270708bd30327f2596253f510370d 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Bundle.php +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Block/Adminhtml/Catalog/Product/Edit/Section/Bundle.php @@ -8,7 +8,6 @@ namespace Magento\Bundle\Test\Block\Adminhtml\Catalog\Product\Edit\Section; use Magento\Mtf\Client\Element\SimpleElement; use Magento\Bundle\Test\Block\Adminhtml\Catalog\Product\Edit\Section\Bundle\Option; -use Magento\Mtf\Client\Element; use Magento\Mtf\Client\ElementInterface; use Magento\Mtf\Client\Locator; use Magento\Ui\Test\Block\Adminhtml\Section; @@ -53,6 +52,13 @@ class Bundle extends Section */ protected $bundleOptionRow = './tr[%d]'; + /** + * Selector for trash can button in bundle option row. + * + * @var string + */ + protected $deleteOption = './tr[%d]//*[@data-index="delete_button"]'; + /** * Get bundle options block. * @@ -83,17 +89,49 @@ class Bundle extends Section if (!isset($fields['bundle_selections'])) { return $this; } + + $context = $this->_rootElement->find($this->bundleOptions, Locator::SELECTOR_XPATH); + + if (isset($fields['bundle_selections']['value']['bundle_options'])) { + foreach ($fields['bundle_selections']['value']['bundle_options'] as $key => $bundleOption) { + $count = $key + 1; + $itemOption = $context->find(sprintf($this->openOption, $count), Locator::SELECTOR_XPATH); + $isContent = $context->find(sprintf($this->optionContent, $count), Locator::SELECTOR_XPATH) + ->isVisible(); + if ($itemOption->isVisible() && !$isContent) { + $itemOption->click(); + } elseif (!$itemOption->isVisible()) { + $this->_rootElement->find($this->addNewOption)->click(); + } + $this->getBundleOptionBlock($count, $context)->fillOption($bundleOption); + } + } + + if (isset($fields['bundle_selections']['value']['bundle_options_delete'])) { + $this->deleteFieldsData($fields['bundle_selections']['value']['bundle_options_delete']); + } + + return $this; + } + + /** + * Delete some bundle options. + * + * @param array $fields + * @return $this + */ + public function deleteFieldsData(array $fields) + { $context = $this->_rootElement->find($this->bundleOptions, Locator::SELECTOR_XPATH); - foreach ($fields['bundle_selections']['value']['bundle_options'] as $key => $bundleOption) { - $count = $key + 1; - $itemOption = $context->find(sprintf($this->openOption, $count), Locator::SELECTOR_XPATH); - $isContent = $context->find(sprintf($this->optionContent, $count), Locator::SELECTOR_XPATH)->isVisible(); - if ($itemOption->isVisible() && !$isContent) { - $itemOption->click(); - } elseif (!$itemOption->isVisible()) { - $this->_rootElement->find($this->addNewOption)->click(); + foreach (array_keys($fields) as $key) { + $bundleOptionIndex = $key + 1; + $deleteOption = $context->find( + sprintf($this->deleteOption, $bundleOptionIndex), + Locator::SELECTOR_XPATH + ); + if ($deleteOption->isVisible()) { + $deleteOption->click(); } - $this->getBundleOptionBlock($count, $context)->fillOption($bundleOption); } return $this; } diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleOptionsDeleted.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleOptionsDeleted.php new file mode 100644 index 0000000000000000000000000000000000000000..69e694abd261ab4ca4e4102f542b72f612552d5b --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Constraint/AssertBundleOptionsDeleted.php @@ -0,0 +1,78 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Bundle\Test\Constraint; + +use Magento\Bundle\Test\Fixture\BundleProduct; +use Magento\Mtf\Constraint\AbstractConstraint; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductEdit; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; +use Magento\Mtf\Fixture\FixtureInterface; + +/** + * Assert bundle product form. + */ +class AssertBundleOptionsDeleted extends AbstractConstraint +{ + /** + * Assert that displayed price view for bundle product on product page equals passed from fixture. + * @param BundleProduct $product + * @param BundleProduct $originalProduct + * @param CatalogProductIndex $productGrid + * @param CatalogProductEdit $productPage + * @return void + */ + public function processAssert( + BundleProduct $product, + BundleProduct $originalProduct, + CatalogProductIndex $productGrid, + CatalogProductEdit $productPage + ) { + $filter = ['sku' => $product->getSku()]; + $productGrid->open(); + $productGrid->getProductGrid()->searchAndOpen($filter); + + $productData = $product->getData()['bundle_selections']['bundle_options']; + $originalProductData = $originalProduct->getData()['bundle_selections']['bundle_options']; + $formData = $productPage->getProductForm()->getData($product)['bundle_selections']; + + $productDataLength = count($productData); + $formDataLength = count($productData); + \PHPUnit_Framework_Assert::assertEquals($productDataLength, $formDataLength); + + foreach ($productData as $index => $option) { + $productAssociatedDataLength = count($option['assigned_products']); + $formAssociatedDataLength = count($formData[$index]['assigned_products']); + \PHPUnit_Framework_Assert::assertEquals($productAssociatedDataLength, $formAssociatedDataLength); + + foreach ($option['assigned_products'] as $productIndex => $associatedProduct) { + $associatedProduct['data']['getProductName'] = + $originalProductData[$index+1]['assigned_products'][$productIndex]['search_data']['name']; + $associatedProduct = $associatedProduct['data']; + $errorAssociatedProducts = array_diff( + $associatedProduct, + $formData[$index]['assigned_products'][$productIndex] + ); + \PHPUnit_Framework_Assert::assertCount(0, $errorAssociatedProducts); + } + + unset($option['assigned_products']); + unset($formData[$index]['assigned_products']); + $errorFields = array_diff($option, $formData[$index]); + \PHPUnit_Framework_Assert::assertCount(0, $errorFields); + } + } + + /** + * Returns a string representation of the object + * + * @return string + */ + public function toString() + { + return 'Bundle options were not deleted correctly. There is difference with expected options'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml index 2b5e9a437b32bdec615fd9e2a928b3784ba68fb4..6a507a7a99b437a793b4096f8f10d9e33ccb825a 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct.xml @@ -225,5 +225,27 @@ <item name="dataset" xsi:type="string">bundle_fixed_100_dollar</item> </field> </dataset> + + <dataset name="with_3_bundle_options"> + <field name="name" xsi:type="string">Bundle with 3 options %isolation%</field> + <field name="url_key" xsi:type="string">with_3_bundle_options-%isolation%</field> + <field name="sku" xsi:type="string">sku_with_3_bundle_options_%isolation%</field> + <field name="sku_type" xsi:type="string">No</field> + <field name="price_type" xsi:type="string">No</field> + <field name="price" xsi:type="array"> + <item name="value" xsi:type="string">100</item> + </field> + <field name="tax_class_id" xsi:type="array"> + <item name="dataset" xsi:type="string">taxable_goods</item> + </field> + <field name="website_ids" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </item> + </field> + <field name="bundle_selections" xsi:type="array"> + <item name="dataset" xsi:type="string">with_3_options</item> + </field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml index 3e9b055c48ca5a1bfa5df3410ec48ef708a71fa7..7131aab3cffcbb69ca84f1078540a2dbada2ae28 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/Repository/BundleProduct/BundleSelections.xml @@ -665,5 +665,170 @@ </item> </field> </dataset> + + <dataset name="with_3_options"> + <field name="bundle_options" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="title" xsi:type="string">Option 1</item> + <item name="type" xsi:type="string">Drop-down</item> + <item name="required" xsi:type="string">No</item> + <item name="assigned_products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">1</item> + <item name="selection_price_value" xsi:type="string">1</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + </item> + </item> + <item name="1" xsi:type="array"> + <item name="title" xsi:type="string">Option 2</item> + <item name="type" xsi:type="string">Radio Buttons</item> + <item name="required" xsi:type="string">No</item> + <item name="assigned_products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">20</item> + <item name="selection_price_value" xsi:type="string">20</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + <item name="1" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">21</item> + <item name="selection_price_value" xsi:type="string">21</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + <item name="2" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">22</item> + <item name="selection_price_value" xsi:type="string">22</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + </item> + </item> + <item name="2" xsi:type="array"> + <item name="title" xsi:type="string">Option 3</item> + <item name="type" xsi:type="string">Drop-down</item> + <item name="required" xsi:type="string">No</item> + <item name="assigned_products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">3</item> + <item name="selection_price_value" xsi:type="string">3</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + </item> + </item> + </field> + <field name="products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="0" xsi:type="string">catalogProductSimple::default</item> + </item> + <item name="1" xsi:type="array"> + <item name="0" xsi:type="string">catalogProductSimple::default</item> + <item name="1" xsi:type="string">catalogProductSimple::product_100_dollar</item> + <item name="2" xsi:type="string">catalogProductSimple::product_without_category</item> + </item> + <item name="2" xsi:type="array"> + <item name="0" xsi:type="string">catalogProductSimple::default</item> + </item> + </field> + </dataset> + + <dataset name="with_3_options_delete"> + <field name="bundle_options_delete" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="title" xsi:type="string">Option 1</item> + <item name="type" xsi:type="string">Drop-down</item> + <item name="required" xsi:type="string">No</item> + </item> + </field> + <field name="bundle_options" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="title" xsi:type="string">Option 2</item> + <item name="type" xsi:type="string">Radio Buttons</item> + <item name="required" xsi:type="string">No</item> + <item name="assigned_products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">20</item> + <item name="selection_price_value" xsi:type="string">20</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + <item name="1" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">21</item> + <item name="selection_price_value" xsi:type="string">21</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + <item name="2" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">22</item> + <item name="selection_price_value" xsi:type="string">22</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + </item> + </item> + <item name="1" xsi:type="array"> + <item name="title" xsi:type="string">Option 3</item> + <item name="type" xsi:type="string">Drop-down</item> + <item name="required" xsi:type="string">No</item> + <item name="assigned_products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="search_data" xsi:type="array"> + <item name="name" xsi:type="string">%product_name%</item> + </item> + <item name="data" xsi:type="array"> + <item name="selection_qty" xsi:type="string">3</item> + <item name="selection_price_value" xsi:type="string">3</item> + <item name="selection_price_type" xsi:type="string">Fixed</item> + </item> + </item> + </item> + </item> + </field> + <field name="products" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="0" xsi:type="string">catalogProductSimple::default</item> + <item name="1" xsi:type="string">catalogProductSimple::product_100_dollar</item> + <item name="2" xsi:type="string">catalogProductSimple::product_without_category</item> + </item> + <item name="1" xsi:type="array"> + <item name="0" xsi:type="string">catalogProductSimple::default</item> + </item> + </field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml index 678e9306cbadb3bc22dafd9bca089a8b7f304ba7..211e09861995f27e22a446b93e465a7197936965 100644 --- a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml @@ -195,8 +195,8 @@ <data name="product/data/price_type" xsi:type="string">Yes</data> <data name="product/data/price/dataset" xsi:type="string">dynamic-8</data> <data name="product/data/special_price" xsi:type="string">20</data> - <data name="product/data/special_from_date/pattern" xsi:type="string">M j, Y -1 day</data> - <data name="product/data/special_to_date/pattern" xsi:type="string">M j, Y +3 days</data> + <data name="product/data/special_from_date/pattern" xsi:type="string">m/d/y -1 day</data> + <data name="product/data/special_to_date/pattern" xsi:type="string">m/d/y +3 days</data> <data name="product/data/bundle_selections/dataset" xsi:type="string">default_dynamic</data> <data name="product/data/bundle_selections/products" xsi:type="string">catalogProductSimple::product_100_dollar,catalogProductSimple::product_40_dollar</data> <data name="product/data/checkout_data/dataset" xsi:type="string">bundle_default</data> diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.php b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3c17360200f44f2fe8875590578c6960ea7757d0 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.php @@ -0,0 +1,94 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Bundle\Test\TestCase; + +use Magento\Bundle\Test\Fixture\BundleProduct; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductEdit; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; +use Magento\Mtf\TestCase\Injectable; + +/** + * Test Flow: + * 1. Login as admin + * 2. Navigate to the Products>Inventory>Catalog + * 3. Click on Bundle product in the grid to edit it + * 4. Fill in some Bundle Options data according to data set + * 5. Delete some Bundle Options data according to data set + * 6. Save product + * 7. Verify Bundle Options in the updated product + * + * @group Bundle_Product + * @ZephyrId MAGETWO-26195 + */ +class UpdateBundleOptionsTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + /* end tags */ + + /** + * Page product on backend + * + * @var CatalogProductIndex + */ + protected $catalogProductIndex; + + /** + * Edit page on backend + * + * @var CatalogProductEdit + */ + protected $catalogProductEdit; + + /** + * Injection data + * + * @param CatalogProductIndex $catalogProductIndexNewPage + * @param CatalogProductEdit $catalogProductEditPage + * @return void + */ + public function __inject( + CatalogProductIndex $catalogProductIndexNewPage, + CatalogProductEdit $catalogProductEditPage + ) { + $this->catalogProductIndex = $catalogProductIndexNewPage; + $this->catalogProductEdit = $catalogProductEditPage; + } + + /** + * Test update bundle product + * + * @param BundleProduct $product + * @param BundleProduct $originalProduct + * @return void + */ + public function test(BundleProduct $product, BundleProduct $originalProduct) + { + // Preconditions + $originalProduct->persist(); + + // Steps + $filter = ['sku' => $originalProduct->getSku()]; + + $this->catalogProductIndex->open(); + $this->catalogProductIndex->getProductGrid()->searchAndOpen($filter); + + $form = $this->catalogProductEdit->getProductForm(); + $form->openSection('bundle'); + $container = $form->getSection('bundle'); + $containerFields = $product->getData()['bundle_selections']['bundle_options_delete']; + $container->deleteFieldsData($containerFields); + + $form->openSection('product-details'); + $container = $form->getSection('product-details'); + $containerFields = $product->getData(); + unset($containerFields['bundle_selections']); + $container->setFieldsData($containerFields); + + $this->catalogProductEdit->getFormPageActions()->save(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..795665d66d2bf7d34438381d3390e94c8458d3b7 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/UpdateBundleOptionsTest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Bundle\Test\TestCase\UpdateBundleOptionsTest" summary="Update Bundle Product Options" ticketId="MAGETWO-26195"> + <variation name="UpdateBundleOptionsTestVariation1"> + <data name="description" xsi:type="string">Update bundle product options</data> + <data name="originalProduct/dataset" xsi:type="string">with_3_bundle_options</data> + <data name="product/data/name" xsi:type="string">bundle_3_options_%isolation%</data> + <data name="product/data/sku" xsi:type="string">bundle_3_options_%isolation%</data> + <data name="product/data/bundle_selections/dataset" xsi:type="string">with_3_options_delete</data> + <constraint name="Magento\Bundle\Test\Constraint\AssertBundleOptionsDeleted" /> + </variation> + </testCase> +</config> \ No newline at end of file diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml index ecd68593b08cc252e67a144ccd2a9610ac88f22d..e1cbc1a1d8ba2dba7f3be971b3f1b456f6e7d02a 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/CatalogProductSimple.xml @@ -1233,5 +1233,35 @@ <field name="url_key" xsi:type="string">overnight-duffle</field> </dataset> + <dataset name="simple_with_weight_10_for_salesrule"> + <field name="attribute_set_id" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </field> + <field name="name" xsi:type="string">Simple Product %isolation%</field> + <field name="sku" xsi:type="string">sku_simple_product_%isolation%</field> + <field name="product_has_weight" xsi:type="string">This item has weight</field> + <field name="weight" xsi:type="string">10</field> + <field name="quantity_and_stock_status" xsi:type="array"> + <item name="qty" xsi:type="string">25</item> + <item name="is_in_stock" xsi:type="string">In Stock</item> + </field> + <field name="price" xsi:type="array"> + <item name="value" xsi:type="string">560</item> + </field> + <field name="tax_class_id" xsi:type="array"> + <item name="dataset" xsi:type="string">taxable_goods</item> + </field> + <field name="website_ids" xsi:type="array"> + <item name="0" xsi:type="array"> + <item name="dataset" xsi:type="string">default</item> + </item> + </field> + <field name="visibility" xsi:type="string">Catalog, Search</field> + <field name="url_key" xsi:type="string">simple-product-%isolation%</field> + <field name="checkout_data" xsi:type="array"> + <item name="dataset" xsi:type="string">simple_order_default</item> + </field> + </dataset> + </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/ConfigData.xml index 9ae1e2ebfacca405f8505496f546cf2182e2dd3f..c32999a6ebce7e0a4c44c1a10bc8fe4010a795f7 100644 --- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/ConfigData.xml +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/ConfigData.xml @@ -34,5 +34,16 @@ <item name="inherit" xsi:type="number">1</item> </field> </dataset> + <dataset name="attribute_product_mask_sku"> + <field name="catalog/fields_masks/sku" xsi:type="array"> + <item name="value" xsi:type="string">{{name}} {{country_of_manufacture}}</item> + </field> + </dataset> + <dataset name="attribute_product_mask_sku_rollback"> + <field name="catalog/fields_masks/sku" xsi:type="array"> + <item name="value" xsi:type="string">{{name}}</item> + <item name="inherit" xsi:type="number">1</item> + </field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.php b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0884e93387b2ac60c0be00e56791ae2b03443d38 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Catalog\Test\TestCase\Product; + +use Magento\Catalog\Test\Fixture\CatalogProductSimple; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductIndex; +use Magento\Catalog\Test\Page\Adminhtml\CatalogProductNew; +use Magento\Mtf\TestCase\Injectable; + +/** + * Steps: + * 1. Login to the backend. + * 2. Navigate to Products > Catalog. + * 3. Start to create simple product. + * 4. Fill in data according to data set. + * 5. Save Product. + * 6. Perform appropriate assertions. + * + * @group Products + * @ZephyrId MAGETWO-59861 + */ +class CreateSimpleProductEntityByAttributeMaskSkuTest extends Injectable +{ + /** + * Configuration setting. + * + * @var string + */ + protected $configData; + + /** + * Should cache be flushed + * + * @var bool + */ + private $flushCache; + + /** + * @var \Magento\Mtf\Fixture\FixtureFactory + */ + private $fixtureFactory; + + /** + * Run create product simple entity by attribute mask SKU test. + * + * @param CatalogProductSimple $product + * @param CatalogProductIndex $productGrid + * @param CatalogProductNew $newProductPage + * @param string $configData + * @param bool $flushCache + * @return array + */ + public function testCreate( + CatalogProductSimple $product, + CatalogProductIndex $productGrid, + CatalogProductNew $newProductPage, + \Magento\Mtf\Fixture\FixtureFactory $fixtureFactory, + $flushCache = false, + $configData = null + ) { + $this->configData = $configData; + $this->flushCache = $flushCache; + $this->fixtureFactory = $fixtureFactory; + + // Preconditions + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData, 'flushCache' => $this->flushCache] + )->run(); + + // Steps + $productGrid->open(); + $productGrid->getGridPageActionBlock()->addProduct('simple'); + $newProductPage->getProductForm()->fill($product); + $newProductPage->getFormPageActions()->save(); + + $skuMask = $this->prepareSkuByMask($product); + + $productSimple = $fixtureFactory->createByCode( + 'catalogProductSimple', + ['data' => array_merge($product->getData(), ['sku' => $skuMask])] + ); + + return ['product' => $productSimple]; + } + + /** + * Obtains product sku based on attributes define in Stores > Configuration->Catalog > Catalog > Mask for SKU + * + * @param CatalogProductSimple $product + * @return string + */ + private function prepareSkuByMask(CatalogProductSimple $product) + { + $productData = $product->getData(); + $skuMask = ''; + $config = $this->fixtureFactory->createByCode('configData', ['dataset' => $this->configData]); + $section = $config->getData('section'); + if (is_array($section) && array_key_exists('catalog/fields_masks/sku', $section)) { + $skuMask = $section['catalog/fields_masks/sku']['value']; + } + + $attributesInPattern = []; + $count = preg_match_all('/{{(\w+)}}/', $skuMask, $matches); + if ($count > 0 && is_array($matches[0])) { + foreach ($matches[1] as $attributeName) { + if (array_key_exists($attributeName, $productData)) { + $attributesInPattern[$attributeName] = $productData[$attributeName]; + } + } + } + foreach ($attributesInPattern as $attributeName => $attributeValue) { + $skuMask = str_replace('{{' . $attributeName . '}}', $attributeValue, $skuMask); + } + return $skuMask; + } + + /** + * Clean data after running test. + * + * @return void + */ + public function tearDown() + { + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData, 'rollback' => true, 'flushCache' => $this->flushCache] + )->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..338f34a57ab5d8745a9cf53665d34e39a0369694 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/CreateSimpleProductEntityByAttributeMaskSkuTest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Catalog\Test\TestCase\Product\CreateSimpleProductEntityByAttributeMaskSkuTest" summary="Create Simple Product with attribute sku mask" ticketId="MAGETWO-59861"> + <variation name="CreateSimpleProductEntityByAttributeMaskSkuTest1"> + <data name="configData" xsi:type="string">attribute_product_mask_sku</data> + <data name="description" xsi:type="string">Create product with country of manufacture attribute sku mask</data> + <data name="product/data/url_key" xsi:type="string">simple-product-%isolation%</data> + <data name="product/data/name" xsi:type="string">Simple Product %isolation%</data> + <data name="product/data/price/value" xsi:type="string">10000</data> + <data name="product/data/weight" xsi:type="string">50</data> + <data name="product/data/quantity_and_stock_status/qty" xsi:type="string">657</data> + <data name="product/data/country_of_manufacture" xsi:type="string">Ukraine</data> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductSaveMessage" /> + <constraint name="Magento\Catalog\Test\Constraint\AssertProductInGrid" /> + </variation> + </testCase> +</config> 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 697733fd3c33d8856f5d775452aca8ad977e77e9..fac0f811be2616675e85e3f34927731f13ea16b1 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 @@ -99,6 +99,13 @@ class Cart extends Block */ protected $preloaderSpinner = '#preloaderSpinner'; + /** + * Cart item class name. + * + * @var string + */ + protected $cartItemClass = \Magento\Checkout\Test\Block\Cart\CartItem::class; + /** * Wait for PayPal page is loaded. * @@ -129,7 +136,7 @@ class Cart extends Block Locator::SELECTOR_XPATH ); $cartItem = $this->blockFactory->create( - '\\' . get_class($this) . '\CartItem', + $this->cartItemClass, ['element' => $cartItemBlock] ); } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Payment.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Payment.php index f1776d2bf933e17d63006948795418309348ffe3..cad6dda90459845718bc3ab1e3ac2952cca16640 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Payment.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Payment.php @@ -85,6 +85,7 @@ class Payment extends Block $paymentLabelSelector = sprintf($this->paymentMethodLabel, $payment['method']); try { + $this->waitForElementNotVisible($this->waitElement); $this->waitForElementVisible($paymentLabelSelector); } catch (\Exception $exception) { throw new \Exception('Such payment method is absent.'); diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.php index a3eda5ea1b3c5a3d6b5be217df906ca69b67f742..f2530c016b714bf5af16c2b6d0f0f6d690cc4af0 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping.php @@ -6,12 +6,67 @@ namespace Magento\Checkout\Test\Block\Onepage; +use Magento\Checkout\Test\Block\Onepage\Shipping\AddressModal; use Magento\Mtf\Block\Form; +use Magento\Mtf\Client\Locator; /** * Checkout shipping address block. */ class Shipping extends Form { - // + /** + * CSS Selector for "New Address" button + * + * @var string + */ + private $newAddressButton = '[data-bind*="isNewAddressAdded"]'; + + /** + * Wait element. + * + * @var string + */ + private $waitElement = '.loading-mask'; + + /** + * SCC Selector for Address Modal block. + * + * @var string + */ + private $addressModalBlock = '//*[@id="opc-new-shipping-address"]/../..'; + + /** + * Click on "New Address" button. + * + * @return void + */ + public function clickOnNewAddressButton() + { + $this->waitForElementNotVisible($this->waitElement); + $this->_rootElement->find($this->newAddressButton)->click(); + } + + /** + * Get Address Modal Block. + * + * @return AddressModal + */ + public function getAddressModalBlock() + { + return $this->blockFactory->create( + AddressModal::class, + ['element' => $this->browser->find($this->addressModalBlock, Locator::SELECTOR_XPATH)] + ); + } + + /** + * Returns form's required elements + * + * @return \Magento\Mtf\Client\ElementInterface[] + */ + public function getRequiredFields() + { + return $this->_rootElement->getElements("div .field._required"); + } } diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping/AddressModal.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping/AddressModal.php new file mode 100644 index 0000000000000000000000000000000000000000..8a949c474bc847942f1bd5922d00514cd6133d08 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping/AddressModal.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Checkout\Test\Block\Onepage\Shipping; + +use Magento\Mtf\Block\Form; + +/** + * Checkout shipping address modal block. + */ +class AddressModal extends Form +{ + /** + * CSS Selector for Save button. + * + * @var string + */ + private $saveButton = '.action-save-address'; + + /** + * Selector for field's error message. + * + * @var string + */ + private $errorMessage = '.field-error'; + + /** + * Selector for error fields. + * + * @var string + */ + private $errorField = '._error'; + + /** + * Selector for field label that have error message. + * + * @var string + */ + private $fieldLabel = '.label'; + + /** + * Click on 'Save Address' button. + * + * @return void + */ + public function save() + { + $this->_rootElement->find($this->saveButton)->click(); + } + + /** + * Get Error messages for attributes. + * + * @return array + */ + public function getErrorMessages() + { + $result = []; + foreach ($this->_rootElement->getElements($this->errorField) as $item) { + $result[$item->find($this->fieldLabel)->getText()] = $item->find($this->errorMessage)->getText(); + } + + return $result; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping/AddressModal.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping/AddressModal.xml new file mode 100644 index 0000000000000000000000000000000000000000..13403b792684512c9741bca74a78f7c84036eb48 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Block/Onepage/Shipping/AddressModal.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" ?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<mapping strict="0"> + <fields> + <firstname /> + <lastname /> + <company /> + <street> + <selector>input[name="street[0]"]</selector> + </street> + <city /> + <region_id> + <input>select</input> + </region_id> + <country_id> + <input>select</input> + </country_id> + <telephone /> + <postcode /> + </fields> +</mapping> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertGrandTotalOrderReview.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertGrandTotalOrderReview.php index 3bf6bda89ad939ad4ae100006996cf4077e1aeb9..dc1433506f2623d2122f293b063a0707017edc13 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertGrandTotalOrderReview.php +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertGrandTotalOrderReview.php @@ -14,6 +14,13 @@ use Magento\Mtf\Constraint\AbstractConstraint; */ class AssertGrandTotalOrderReview extends AbstractConstraint { + /** + * Wait element. + * + * @var string + */ + protected $waitElement = '.loading-mask'; + /** * Assert that Order Grand Total is correct on checkoutOnePage * @@ -23,6 +30,7 @@ class AssertGrandTotalOrderReview extends AbstractConstraint */ public function processAssert(CheckoutOnepage $checkoutOnepage, $grandTotal) { + $checkoutOnepage->getReviewBlock()->waitForElementNotVisible($this->waitElement); $checkoutReviewGrandTotal = $checkoutOnepage->getReviewBlock()->getGrandTotal(); \PHPUnit_Framework_Assert::assertEquals( diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertShippingAddressJsValidationMessagesIsAbsent.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertShippingAddressJsValidationMessagesIsAbsent.php new file mode 100644 index 0000000000000000000000000000000000000000..f4f46a1c2b7c2577a0f61882f9d707397b1c1a20 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/Constraint/AssertShippingAddressJsValidationMessagesIsAbsent.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Checkout\Test\Constraint; + +use Magento\Checkout\Test\Page\CheckoutOnepage; +use Magento\Mtf\Constraint\AbstractConstraint; + +/** + * Class AssertShippingAddressJsValidationMessagesIsAbsent + * Assert js validation messages are absent for required fields. + */ +class AssertShippingAddressJsValidationMessagesIsAbsent extends AbstractConstraint +{ + /** + * Assert js validation messages are absent for required fields. + * + * @param CheckoutOnepage $checkoutOnepage + * @return void + */ + public function processAssert(CheckoutOnepage $checkoutOnepage) + { + $requiredFields = $checkoutOnepage->getShippingBlock()->getRequiredFields(); + + /** @var \Magento\Mtf\Client\ElementInterface $field */ + foreach ($requiredFields as $field) { + $errorContainer = $field->find("div .field-error"); + \PHPUnit_Framework_Assert::assertFalse( + $errorContainer->isVisible(), + 'Js validation error messages must be absent for required fields after checkout start.' + ); + } + } + + /** + * Returns string representation of successful assertion + * + * @return string + */ + public function toString() + { + return 'Js validation messages are absent for required fields.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5361ee6e3c127bafc03e53cd717ac067688f826a --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.php @@ -0,0 +1,33 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Checkout\Test\TestCase; + +use Magento\Mtf\TestCase\Scenario; + +/** + * Steps: + * 1. Go to Frontend as guest. + * 2. Add simple product to shopping cart + * 3. Go to shopping cart page + * 4. Proceed to checkout + * 5. Perform assertions. + * + * @group One_Page_Checkout + * @ZephyrId MAGETWO-59697 + */ +class OnePageCheckoutJsValidationTest extends Scenario +{ + /** + * Runs one page checkout js validation test. + * + * @return void + */ + public function test() + { + $this->executeScenario(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..e930ff29882ceed4e8f99947a6a6ef4b4acc1c7a --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/OnePageCheckoutJsValidationTest.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Checkout\Test\TestCase\OnePageCheckoutJsValidationTest" summary="JS validation verification for Checkout flow" ticketId="MAGETWO-59697"> + <variation name="OnePageCheckoutJsValidationTestVariation1" summary="JS validation is not applied for empty required checkout fields if customer did not fill them"> + <data name="products/0" xsi:type="string">catalogProductSimple::default</data> + <data name="checkoutMethod" xsi:type="string">guest</data> + <constraint name="Magento\Checkout\Test\Constraint\AssertShippingAddressJsValidationMessagesIsAbsent" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddNewShippingAddressStep.php b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddNewShippingAddressStep.php new file mode 100644 index 0000000000000000000000000000000000000000..a4ae00510a9de0b2e9e7d82ff3285e0ae780a7cc --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestStep/AddNewShippingAddressStep.php @@ -0,0 +1,57 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Checkout\Test\TestStep; + +use Magento\Checkout\Test\Page\CheckoutOnepage; +use Magento\Customer\Test\Fixture\Address; +use Magento\Mtf\TestStep\TestStepInterface; + +/** + * Create customer custom attribute step. + */ +class AddNewShippingAddressStep implements TestStepInterface +{ + /** + * Checkout One page. + * + * @var CheckoutOnepage + */ + private $checkoutOnepage; + + /** + * Shipping Address fixture. + * + * @var Address + */ + private $address; + + /** + * @constructor + * @param CheckoutOnepage $checkoutOnepage + * @param Address|null $address [optional] + */ + public function __construct(CheckoutOnepage $checkoutOnepage, Address $address = null) + { + $this->checkoutOnepage = $checkoutOnepage; + $this->address = $address; + } + + /** + * Create customer account. + * + * @return void + */ + public function run() + { + $shippingBlock = $this->checkoutOnepage->getShippingBlock(); + $shippingBlock->clickOnNewAddressButton(); + if ($this->address) { + $shippingBlock->getAddressModalBlock()->fill($this->address); + } + $shippingBlock->getAddressModalBlock()->save(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/etc/testcase.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/etc/testcase.xml index a26a2cead49b07a094e550db1207e36d53269b15..24eb96c0a9347741384ffd6718bf349eeb452c85 100644 --- a/dev/tests/functional/tests/app/Magento/Checkout/Test/etc/testcase.xml +++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/etc/testcase.xml @@ -22,4 +22,10 @@ <step name="placeOrder" module="Magento_Checkout" next="createCustomerAccount" /> <step name="createCustomerAccount" module="Magento_Checkout" /> </scenario> + <scenario name="OnePageCheckoutJsValidationTest" firstStep="setupConfiguration"> + <step name="setupConfiguration" module="Magento_Config" next="createProducts" /> + <step name="createProducts" module="Magento_Catalog" next="addProductsToTheCart" /> + <step name="addProductsToTheCart" module="Magento_Checkout" next="ProceedToCheckout" /> + <step name="ProceedToCheckout" module="Magento_Checkout" /> + </scenario> </config> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductPage.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductPage.php index 62d6c72efe8c6aa8d0a8ed4aff4594316318c9e3..ad9807b76d6e532fd00e9f22fac7bf4007a80502 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductPage.php +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Constraint/AssertConfigurableProductPage.php @@ -125,15 +125,23 @@ class AssertConfigurableProductPage extends AssertProductPage protected function getLowestConfigurablePrice() { $price = null; - $configurableOptions = $this->product->getConfigurableAttributesData(); - - foreach ($configurableOptions['matrix'] as $option) { - $price = $price === null ? $option['price'] : $price; - if ($price > $option['price']) { - $price = $option['price']; + $priceDataConfig = $this->product->getDataFieldConfig('price'); + if (isset($priceDataConfig['source'])) { + $priceData = $priceDataConfig['source']->getPriceData(); + if (isset($priceData['price_from'])) { + $price = $priceData['price_from']; } } + if (null === $price) { + $configurableOptions = $this->product->getConfigurableAttributesData(); + foreach ($configurableOptions['matrix'] as $option) { + $price = $price === null ? $option['price'] : $price; + if ($price > $option['price']) { + $price = $option['price']; + } + } + } return $price; } } diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/Price.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/Price.xml index 2e9d708dd42d82ca79d0507b6a698c0c5c897d3b..1b6282b9432c39fd444ca2089f1b8c8aa6bfb876 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/Price.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Repository/ConfigurableProduct/Price.xml @@ -17,5 +17,8 @@ <dataset name="MAGETWO-12620"> <field name="category_price" xsi:type="string">11</field> </dataset> + <dataset name="from-9"> + <field name="price_from" xsi:type="string">9</field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml index 58435b066261ed28cc7f33ff01fc250940b6eb17..187283da4602fd56c6fb6f2441d11dc5c1ad05bd 100644 --- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml @@ -58,6 +58,7 @@ <data name="product/data/checkout_data/dataset" xsi:type="string">configurable_two_new_options_with_special_price</data> <data name="product/data/name" xsi:type="string">Configurable Product %isolation%</data> <data name="product/data/sku" xsi:type="string">configurable_sku_%isolation%</data> + <data name="product/data/price/dataset" xsi:type="string">from-9</data> <data name="product/data/price/value" xsi:type="string">100</data> <data name="product/data/special_price" xsi:type="string">9</data> <data name="product/data/short_description" xsi:type="string">Configurable short description</data> diff --git a/dev/tests/functional/tests/app/Magento/Install/Test/Block/WebConfiguration.php b/dev/tests/functional/tests/app/Magento/Install/Test/Block/WebConfiguration.php index 03dbfee05ca6973d90da4a4205eb1b64f9f2edca..bf4886a9f208d4a9bf308ee8f5f8b26daf9c6d6e 100644 --- a/dev/tests/functional/tests/app/Magento/Install/Test/Block/WebConfiguration.php +++ b/dev/tests/functional/tests/app/Magento/Install/Test/Block/WebConfiguration.php @@ -21,7 +21,7 @@ class WebConfiguration extends Form * * @var string */ - protected $next = "[ng-click*='next']"; + protected $next = "[ng-click*='validateUrl']"; /** * 'Advanced Options' locator. diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/ExpressCheckout.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/ExpressCheckout.php index d83ae0c53ec0a575ba11280b9a8731bbe23a6f8a..6e4849669568851cbe4787766c6cf8b2f630e9e6 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/ExpressCheckout.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/ExpressCheckout.php @@ -20,8 +20,6 @@ class ExpressCheckout extends Block * @var array */ private $fields = [ - 'Email Associated with PayPal Merchant Account' => '#payment_us_paypal_alternative_payment_methods_express_' . - 'checkout_us_express_checkout_required_express_checkout_required_express_checkout_business_account', 'API Username' => '#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_' . 'required_express_checkout_required_express_checkout_api_username', 'API Password' => '#payment_us_paypal_alternative_payment_methods_express_checkout_us_express_checkout_' . @@ -72,8 +70,6 @@ class ExpressCheckout extends Block */ public function specifyCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account']) - ->setValue('test@test.com'); $this->_rootElement->find($this->fields['API Username'])->setValue('1'); $this->_rootElement->find($this->fields['API Password'])->setValue('1'); $this->_rootElement->find($this->fields['API Signature'])->setValue('1'); @@ -86,7 +82,6 @@ class ExpressCheckout extends Block */ public function clearCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account'])->setValue(''); $this->_rootElement->find($this->fields['API Username'])->setValue(''); $this->_rootElement->find($this->fields['API Password'])->setValue(''); $this->_rootElement->find($this->fields['API Signature'])->setValue(''); diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowLink.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowLink.php index de6eb107c61ce5e6b63451abdea0473351ddde43..f1af6acb6174e1b462651b7887d80159d6910952 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowLink.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowLink.php @@ -20,8 +20,6 @@ class PayflowLink extends Block * @var array */ private $fields = [ - 'Email Associated with PayPal Merchant Account' => '#payment_us_paypal_payment_gateways_payflow_link_us_' . - 'payflow_link_required_payflow_link_payflow_link_business_account', 'Partner' => '#payment_us_paypal_payment_gateways_payflow_link_us_payflow_link_required_payflow_link_payflow_' . 'link_partner', 'Vendor' => '#payment_us_paypal_payment_gateways_payflow_link_us_payflow_link_required_payflow_link_payflow_' . @@ -60,8 +58,6 @@ class PayflowLink extends Block */ public function specifyCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account']) - ->setValue('test@test.com'); $this->_rootElement->find($this->fields['Partner'])->setValue('1'); $this->_rootElement->find($this->fields['Vendor'])->setValue('1'); $this->_rootElement->find($this->fields['User'])->setValue('1'); @@ -75,7 +71,6 @@ class PayflowLink extends Block */ public function clearCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account'])->setValue(''); $this->_rootElement->find($this->fields['Partner'])->setValue(''); $this->_rootElement->find($this->fields['Vendor'])->setValue(''); $this->_rootElement->find($this->fields['User'])->setValue(''); diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php index e3b01aa3af9a0dfa4517ad3fe4b527eec9d30ad0..1db07ac435a37ed9c9d9d237f62b7debed956861 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PayflowPro.php @@ -20,8 +20,6 @@ class PayflowPro extends Block * @var array */ private $fields = [ - 'Email Associated with PayPal Merchant Account' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_' . - 'with_express_checkout_paypal_payflow_required_paypal_payflow_api_settings_business_account', 'Partner' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_with_express_checkout_paypal_payflow_' . 'required_paypal_payflow_api_settings_partner', 'Vendor' => '#payment_us_paypal_payment_gateways_paypal_payflowpro_with_express_checkout_paypal_payflow_' . @@ -60,8 +58,6 @@ class PayflowPro extends Block */ public function specifyCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account']) - ->setValue('test@test.com'); $this->_rootElement->find($this->fields['Partner'])->setValue('1'); $this->_rootElement->find($this->fields['Vendor'])->setValue('1'); $this->_rootElement->find($this->fields['User'])->setValue('1'); @@ -75,7 +71,6 @@ class PayflowPro extends Block */ public function clearCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account'])->setValue(''); $this->_rootElement->find($this->fields['Partner'])->setValue(''); $this->_rootElement->find($this->fields['Vendor'])->setValue(''); $this->_rootElement->find($this->fields['User'])->setValue(''); diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsAdvanced.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsAdvanced.php index cd6d1a5b1b38a016382ffa2e68ab73bd566c4116..87adb729a1646e6eb5b61e8e167d2eed20111e4e 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsAdvanced.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsAdvanced.php @@ -20,8 +20,6 @@ class PaymentsAdvanced extends Block * @var array */ private $fields = [ - 'Email Associated with PayPal Merchant Account' => '#payment_us_paypal_group_all_in_one_payflow_advanced_' . - 'required_settings_payments_advanced_business_account', 'Partner' => '#payment_us_paypal_group_all_in_one_payflow_advanced_required_settings_payments_advanced_partner', 'Vendor' => '#payment_us_paypal_group_all_in_one_payflow_advanced_required_settings_payments_advanced_vendor', 'User' => '#payment_us_paypal_group_all_in_one_payflow_advanced_required_settings_payments_advanced_user', @@ -54,8 +52,6 @@ class PaymentsAdvanced extends Block */ public function specifyCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account']) - ->setValue('test@test.com'); $this->_rootElement->find($this->fields['Partner'])->setValue('1'); $this->_rootElement->find($this->fields['Vendor'])->setValue('1'); $this->_rootElement->find($this->fields['User'])->setValue('1'); @@ -69,7 +65,6 @@ class PaymentsAdvanced extends Block */ public function clearCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account'])->setValue(''); $this->_rootElement->find($this->fields['Partner'])->setValue(''); $this->_rootElement->find($this->fields['Vendor'])->setValue(''); $this->_rootElement->find($this->fields['User'])->setValue(''); diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php index cd146aa8f809cf5de0cbddcb02be83a19e59928f..55fd7444ba50ad8dbf9662b758b10d7a70ee6c6d 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/Block/System/Config/PaymentsPro.php @@ -20,8 +20,6 @@ class PaymentsPro extends Block * @var array */ private $fields = [ - 'Email Associated with PayPal Merchant Account' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_' . - 'payflow_required_paypal_payflow_api_settings_business_account', 'Partner' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_payflow_required_paypal_payflow_api_' . 'settings_partner', 'Vendor' => '#payment_us_paypal_group_all_in_one_wpp_usuk_paypal_payflow_required_paypal_payflow_api_' . @@ -60,8 +58,6 @@ class PaymentsPro extends Block */ public function specifyCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account']) - ->setValue('test@test.com'); $this->_rootElement->find($this->fields['Partner'])->setValue('1'); $this->_rootElement->find($this->fields['Vendor'])->setValue('1'); $this->_rootElement->find($this->fields['User'])->setValue('1'); @@ -75,7 +71,6 @@ class PaymentsPro extends Block */ public function clearCredentials() { - $this->_rootElement->find($this->fields['Email Associated with PayPal Merchant Account'])->setValue(''); $this->_rootElement->find($this->fields['Partner'])->setValue(''); $this->_rootElement->find($this->fields['Vendor'])->setValue(''); $this->_rootElement->find($this->fields['User'])->setValue(''); diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateVaultOrderBackendTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateVaultOrderBackendTest.xml index 38666c36a2ba4e53641f45616060353ca5e5e22c..ca26ad42a020c80ab0540c21a2928b8a1874cae7 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateVaultOrderBackendTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/CreateVaultOrderBackendTest.xml @@ -19,6 +19,7 @@ <item name="grandTotal" xsi:type="string">15.00</item> </data> <data name="payment/method" xsi:type="string">payflowpro</data> + <data name="vault/method" xsi:type="string">payflowpro_cc_vault</data> <data name="creditCardClass" xsi:type="string">credit_card_admin</data> <data name="creditCard/dataset" xsi:type="string">visa_default</data> <data name="creditCardSave" xsi:type="string">Yes</data> diff --git a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml index c33abb1dbdd2263fded6dbbfb0f683848a3bdd3f..b1da43e473bc5d78d4bfaf0fbcbb718dac6acd3e 100644 --- a/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml +++ b/dev/tests/functional/tests/app/Magento/Paypal/Test/TestCase/ReorderUsingVaultTest.xml @@ -19,6 +19,7 @@ <item name="grandTotal" xsi:type="string">15.00</item> </data> <data name="payment/method" xsi:type="string">payflowpro</data> + <data name="vault/method" xsi:type="string">payflowpro_cc_vault</data> <data name="creditCardClass" xsi:type="string">credit_card</data> <data name="creditCard/dataset" xsi:type="string">visa_default</data> <data name="configData" xsi:type="string">payflowpro, payflowpro_use_vault</data> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php index bfe0bc9eabddfb0cc0f746f79efdad5b6ad0a43b..1f3f0e5a300b46ac9a193666796e52c584b8c944 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Constraint/AssertCartPriceRuleForm.php @@ -25,8 +25,6 @@ class AssertCartPriceRuleForm extends AbstractConstraint protected $skippedFields = [ 'conditions_serialized', 'actions_serialized', - 'from_date', - 'to_date', 'rule_id' ]; diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Curl.php b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Curl.php index 5ab10a0e26f79fcded0b42c77562b077cf0b5412..310f52f75e2cf124e07ab1d7d4bf431e48759e8d 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Curl.php +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Handler/SalesRule/Curl.php @@ -70,6 +70,10 @@ class Curl extends Conditions implements SalesRuleInterface 'type' => \Magento\SalesRule\Model\Rule\Condition\Address::class, 'attribute' => 'postcode', ], + 'Total Weight' => [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Address::class, + 'attribute' => 'weight', + ], 'Category' => [ 'type' => \Magento\SalesRule\Model\Rule\Condition\Product::class, 'attribute' => 'category_ids', diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml index 010b9d3233d163f6ec35ed4291bebbe352bbe9c4..3a35215d841d50780d212084d76d014af8708aa4 100644 --- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/Repository/SalesRule.xml @@ -114,10 +114,10 @@ <field name="uses_per_coupon" xsi:type="string">13</field> <field name="uses_per_customer" xsi:type="string">63</field> <field name="from_date" xsi:type="array"> - <item name="pattern" xsi:type="string">3/25/2014</item> + <item name="pattern" xsi:type="string">03/25/2014</item> </field> <field name="to_date" xsi:type="array"> - <item name="pattern" xsi:type="string">6/29/2024</item> + <item name="pattern" xsi:type="string">-</item> </field> <field name="sort_order" xsi:type="string">1</field> <field name="is_rss" xsi:type="string">Yes</field> @@ -279,5 +279,24 @@ <field name="stop_rules_processing" xsi:type="string">No</field> <field name="simple_free_shipping" xsi:type="string">No</field> </dataset> + <dataset name="rule_with_freeshipping_by_weight"> + <field name="name" xsi:type="string">Cart price rule with free shipping by weight</field> + <field name="is_active" xsi:type="string">Yes</field> + <field name="website_ids" xsi:type="array"> + <item name="0" xsi:type="string">Main Website</item> + </field> + <field name="customer_group_ids" xsi:type="array"> + <item name="0" xsi:type="string">NOT LOGGED IN</item> + </field> + <field name="coupon_type" xsi:type="string">No Coupon</field> + <field name="sort_order" xsi:type="string">1</field> + <field name="is_rss" xsi:type="string">Yes</field> + <field name="conditions_serialized" xsi:type="string">[Total Weight|is|1]</field> + <field name="simple_action" xsi:type="string">Percent of product price discount</field> + <field name="discount_amount" xsi:type="string">0</field> + <field name="apply_to_shipping" xsi:type="string">No</field> + <field name="stop_rules_processing" xsi:type="string">No</field> + <field name="simple_free_shipping" xsi:type="string">For matching items only</field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ShoppingCartWithFreeShippingTest.php b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ShoppingCartWithFreeShippingTest.php new file mode 100644 index 0000000000000000000000000000000000000000..11e510759fdcc5aa4c9368e61c3f5373d32dd9bb --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ShoppingCartWithFreeShippingTest.php @@ -0,0 +1,77 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\SalesRule\Test\TestCase; + +/** + * Precondition: + * 1. Cart Price Rule was created. + * + * Steps: + * 1. Go to storefront + * 2. Add product to shopping cart + * 3. Go to shopping cart page + * 4. Perform asserts. + * + * @group Shopping_Cart_Price_Rules + * @ZephyrId MAGETWO-59665 + */ +class ShoppingCartWithFreeShippingTest extends \Magento\Mtf\TestCase\Injectable +{ + /** + * @var \Magento\Mtf\TestStep\TestStepFactory + */ + private $testStepFactory; + + /** + * Inject data + * + * @param \Magento\Mtf\TestStep\TestStepFactory $testStepFactory + * @return void + */ + public function __inject( + \Magento\Mtf\TestStep\TestStepFactory $testStepFactory + ) { + $this->testStepFactory = $testStepFactory; + } + + /** + * Test sales rule with free shipping applied by product weight + * + * @param \Magento\SalesRule\Test\Fixture\SalesRule $salesRule + * @param \Magento\Catalog\Test\Fixture\CatalogProductSimple $product + * @param \Magento\Checkout\Test\Fixture\Cart $cart + * @return void + */ + public function testRuleWithFreeShippingByWeight( + \Magento\SalesRule\Test\Fixture\SalesRule $salesRule, + \Magento\Catalog\Test\Fixture\CatalogProductSimple $product, + \Magento\Checkout\Test\Fixture\Cart $cart + ) { + $salesRule->persist(); + $product->persist(); + + $this->testStepFactory->create( + \Magento\Checkout\Test\TestStep\AddProductsToTheCartStep::class, + ['products' => [$product]] + )->run(); + + $this->testStepFactory->create( + \Magento\Checkout\Test\TestStep\EstimateShippingAndTaxStep::class, + ['products' => [$product], 'cart' => $cart] + )->run(); + } + + /** + * Clear data after test. + * + * @return void + */ + public function tearDown() + { + $this->testStepFactory->create(\Magento\SalesRule\Test\TestStep\DeleteAllSalesRuleStep::class)->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ShoppingCartWithFreeShippingTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ShoppingCartWithFreeShippingTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..7391d2281f8094b059cfaf9d26115c371d9498b0 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/ShoppingCartWithFreeShippingTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\SalesRule\Test\TestCase\ShoppingCartWithFreeShippingTest" summary="Shopping Cart With Freeshipping" ticketId="MAGETWO-59665"> + <variation name="ShoppingCartWithFreeShippingTestVariation1"> + <data name="salesRule/dataset" xsi:type="string">rule_with_freeshipping_by_weight</data> + <data name="product/dataset" xsi:type="string">default</data> + <data name="cart/data/subtotal" xsi:type="string">560.00</data> + <data name="cart/data/shipping_amount" xsi:type="string">0.00</data> + <data name="cart/data/grand_total" xsi:type="string">560.00</data> + </variation> + <variation name="ShoppingCartWithFreeShippingTestVariation2"> + <data name="salesRule/dataset" xsi:type="string">rule_with_freeshipping_by_weight</data> + <data name="product/dataset" xsi:type="string">simple_with_weight_10_for_salesrule</data> + <data name="cart/data/subtotal" xsi:type="string">560.00</data> + <data name="cart/data/shipping_amount" xsi:type="string">5.00</data> + <data name="cart/data/grand_total" xsi:type="string">565.00</data> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4b42430c0b8a81987b78d907f8b4a4c56d6db834 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.php @@ -0,0 +1,134 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\TestCase; + + +use Magento\User\Test\Page\Adminhtml\UserEdit; +use Magento\User\Test\Page\Adminhtml\UserIndex; +use Magento\Mtf\TestCase\Injectable; +use Magento\User\Test\Fixture\User; +use Magento\Backend\Test\Page\AdminAuthLogin; + +/** + * Preconditions: + * 1. Create admin user. + * 2. Configure 'Maximum Login Failures to Lockout Account'. + * + * Steps: + * 1. Log in to backend as admin user. + * 2. Navigate to System > All Users. + * 3. Click on Add New User. + * 4. Fill in all data according to data set (password is incorrect). + * 5. Perform action 4 specified number of times. + * 6. "You have entered an invalid password for current user." appears after each attempt. + * 7. Perform all assertions. + * + * @ZephyrId MAGETWO-49034 + */ +class LockAdminUserWhenCreatingNewUserTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const SEVERITY = 'S2'; + /* end tags */ + + /** + * User grid page + * + * @var UserIndex + */ + protected $userIndexPage; + + /** + * User new/edit page + * + * @var UserEdit + */ + protected $userEditPage; + + /** + * Configuration setting. + * + * @var string + */ + protected $configData; + + /** + * @var AdminAuthLogin page + */ + protected $adminAuthLogin; + + /** + * Setup data for test. + * @param UserIndex $userIndex + * @param UserEdit $userEdit + * @param AdminAuthLogin $adminAuthLogin + */ + public function __inject( + UserIndex $userIndex, + UserEdit $userEdit, + AdminAuthLogin $adminAuthLogin + ) { + $this->userIndexPage = $userIndex; + $this->userEditPage = $userEdit; + $this->adminAuthLogin = $adminAuthLogin; + } + + /** + * Runs Lock admin user when creating new user test. + * + * @param int $attempts + * @param User $customAdmin, + * @param User $user, + * @param string $configData + * @return void + */ + public function test( + $attempts, + User $customAdmin, + User $user, + $configData + ) { + $this->configData = $configData; + + // Preconditions + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData] + )->run(); + $customAdmin->persist(); + + // Steps + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); + $this->userIndexPage->open(); + $this->userIndexPage->getPageActions()->addNew(); + for ($i = 0; $i < $attempts; $i++) { + $this->userEditPage->getUserForm()->fill($user); + $this->userEditPage->getPageActions()->save(); + } + + // Reload + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); + } + + /** + * Clean data after running test. + * + * @return void + */ + public function tearDown() + { + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData, 'rollback' => true] + )->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..e36f8b4625dd66eeb5a41f15c74c4bdf6e7eeeea --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenCreatingNewUserTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenCreatingNewUserTest" summary="Lock admin user after entering incorrect password while creating new User"> + <variation name="LockAdminUserWhenCreatingNewUserTestVariation1"> + <data name="configData" xsi:type="string">user_lockout_failures</data> + <data name="tag" xsi:type="string">severity:S2</data> + <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data> + <data name="user/data/username" xsi:type="string">AdminUser%isolation%</data> + <data name="user/data/firstname" xsi:type="string">FirstName%isolation%</data> + <data name="user/data/lastname" xsi:type="string">LastName%isolation%</data> + <data name="user/data/email" xsi:type="string">email%isolation%@example.com</data> + <data name="user/data/password" xsi:type="string">123123q</data> + <data name="user/data/password_confirmation" xsi:type="string">123123q</data> + <data name="user/data/current_password" xsi:type="string">incorrect password</data> + <data name="attempts" xsi:type="string">4</data> + <constraint name="Magento\Security\Test\Constraint\AssertUserIsLocked" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.php new file mode 100644 index 0000000000000000000000000000000000000000..4a1c1d0507c91ea3b9a86d254f6f2c01d58a7d2d --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\TestCase; + +use Magento\Integration\Test\Fixture\Integration; +use Magento\User\Test\Fixture\User; +use Magento\Integration\Test\Page\Adminhtml\IntegrationIndex; +use Magento\Integration\Test\Page\Adminhtml\IntegrationNew; +use Magento\Mtf\TestCase\Injectable; +use Magento\Backend\Test\Page\AdminAuthLogin; + +/** + * Preconditions: + * 1. Create admin user. + * 2. Create integration. + * 3. Configure 'Maximum Login Failures to Lockout Account'. + * + * Steps: + * 1. Log in to backend as admin user. + * 2. Navigate to System > Extensions > Integrations. + * 3. Start to edit existing Integration. + * 4. Fill in all data according to data set (password is incorrect). + * 5. Perform action 4 specified number of times. + * 6. "You have entered an invalid password for current user." appears after each attempt. + * 7. Perform all assertions. + * + * @ZephyrId MAGETWO-49039 + */ +class LockAdminUserWhenEditingIntegrationTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const SEVERITY = 'S2'; + /* end tags */ + + /** + * Integration grid page. + * + * @var IntegrationIndex + */ + protected $integrationIndexPage; + + /** + * Integration new page. + * + * @var IntegrationNew + */ + protected $integrationNewPage; + + /** + * Configuration setting. + * + * @var string + */ + protected $configData; + + /** + * @var AdminAuthLogin + */ + protected $adminAuthLogin; + + /** + * Preparing pages for test. + * + * @param IntegrationIndex $integrationIndex + * @param IntegrationNew $integrationNew + * @param AdminAuthLogin $adminAuthLogin + * @return void + */ + public function __inject( + IntegrationIndex $integrationIndex, + IntegrationNew $integrationNew, + AdminAuthLogin $adminAuthLogin + ) { + $this->integrationIndexPage = $integrationIndex; + $this->integrationNewPage = $integrationNew; + $this->adminAuthLogin = $adminAuthLogin; + } + + /** + * Run Lock user when creating new integration test. + * + * @param Integration $initintegration + * @param Integration $integration + * @param int $attempts + * @param User $customAdmin + * @param string $configData + * @return void + */ + public function test( + Integration $initintegration, + Integration $integration, + $attempts, + User $customAdmin, + $configData + ) { + $this->configData = $configData; + + // Preconditions + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData] + )->run(); + $customAdmin->persist(); + $initintegration->persist(); + + // login to backend with new user + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); + + // Steps + $filter = ['name' => $initintegration->getName()]; + $this->integrationIndexPage->open(); + $this->integrationIndexPage->getIntegrationGrid()->searchAndOpen($filter); + for ($i = 0; $i < $attempts; $i++) { + $this->integrationNewPage->getIntegrationForm()->fill($integration); + $this->integrationNewPage->getFormPageActions()->save(); + } + + // Reload page + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); + } + + /** + * Clean data after running test. + * + * @return void + */ + public function tearDown() + { + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData, 'rollback' => true] + )->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..6efc88d78cd3836f00db79d2c567d3e6e38d1bb1 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingIntegrationTest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenEditingIntegrationTest" summary="Lock admin user after entering incorrect password while editing integration"> + <variation name="LockAdminUserWhenCreatingNewIntegrationTestVariation1"> + <data name="configData" xsi:type="string">user_lockout_failures</data> + <data name="tag" xsi:type="string">severity:S2</data> + <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data> + <data name="initintegration/dataset" xsi:type="string">default_active</data> + <data name="integration/data/name" xsi:type="string">Integration%isolation%</data> + <data name="integration/data/current_password" xsi:type="string">incorrect password</data> + <data name="attempts" xsi:type="string">4</data> + <constraint name="Magento\Security\Test\Constraint\AssertUserIsLocked" /> + </variation> + </testCase> +</config> \ No newline at end of file diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.php new file mode 100644 index 0000000000000000000000000000000000000000..bd7c8cef92d8a320b43615983a95ea724cce0d78 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.php @@ -0,0 +1,140 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Security\Test\TestCase; + +use Magento\User\Test\Page\Adminhtml\UserRoleEditRole; +use Magento\User\Test\Page\Adminhtml\UserRoleIndex; +use Magento\Mtf\TestCase\Injectable; +use Magento\User\Test\Fixture\User; +use Magento\User\Test\Fixture\Role; +use Magento\Backend\Test\Page\AdminAuthLogin; + +/** + * Preconditions: + * 1. Create new admin user and assign it to new role. + * 2. Configure 'Maximum Login Failures to Lockout Account'. + * + * Steps: + * 1. Log in to backend as new created admin user. + * 2. Navigate to System > User Roles. + * 3. Start editing existing User Role. + * 4. Fill in all data according to data set (password is incorrect). + * 5. Perform action 4 specified number of times. + * 6. Admin account is locked. + * 7. Perform all assertions. + * + * @ZephyrId MAGETWO-49037 + * @Group Security + * + */ +class LockAdminUserWhenEditingRoleTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const SEVERITY = 'S2'; + /* end tags */ + + /** + * UserRoleIndex page. + * + * @var UserRoleIndex + */ + protected $userRoleIndex; + + /** + * UserRoleEditRole page. + * + * @var UserRoleEditRole + */ + protected $userRoleEditRole; + + /** + * Configuration setting. + * + * @var string + */ + protected $configData; + + /** + * Admin login Page. + * + * @var AdminAuthLogin + */ + protected $adminAuthLogin; + + /** + * Setup data for test. + * + * @param UserRoleIndex $userRoleIndex + * @param UserRoleEditRole $userRoleEditRole + * @param AdminAuthLogin $adminAuthLogin + * @return void + */ + public function __inject( + UserRoleIndex $userRoleIndex, + UserRoleEditRole $userRoleEditRole, + AdminAuthLogin $adminAuthLogin + ) { + $this->userRoleIndex = $userRoleIndex; + $this->userRoleEditRole = $userRoleEditRole; + $this->adminAuthLogin = $adminAuthLogin; + } + + /** + * Runs Lock admin user when editing existing role test. + * + * @param Role $role + * @param Role $initrole + * @param int $attempts + * @param User $customAdmin + * @param string $configData + * @return void + */ + public function test( + Role $role, + Role $initrole, + $attempts, + User $customAdmin, + $configData + ) { + $this->configData = $configData; + // Preconditions + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData] + )->run(); + $customAdmin->persist(); + $initrole->persist(); + // Steps login to backend with new user + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); + $filter = ['rolename' => $initrole->getRolename()]; + $this->userRoleIndex->open(); + $this->userRoleIndex->getRoleGrid()->searchAndOpen($filter); + for ($i = 0; $i < $attempts; $i++) { + $this->userRoleEditRole->getRoleFormTabs()->fill($role); + $this->userRoleEditRole->getPageActions()->save(); + } + // Reload + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); + } + + /** + * Clean data after running test. + * + * @return void + */ + public function tearDown() + { + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData, 'rollback' => true] + )->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..1d081e5d2dd9af69f90aeed8b787c966238e62de --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingRoleTest.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenEditingRoleTest" summary="Lock admin user after entering incorrect password while editing existing role"> + <variation name="LockAdminUserWhenEditingUserRoleTestVariation1"> + <data name="configData" xsi:type="string">user_lockout_failures</data> + <data name="tag" xsi:type="string">severity:S2</data> + <data name="initrole/dataset" xsi:type="string">default</data> + <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data> + <data name="role/data/rolename" xsi:type="string">NewAdminRole%isolation%</data> + <data name="role/data/current_password" xsi:type="string">incorrect password</data> + <data name="role/data/resource_access" xsi:type="string">All</data> + <data name="attempts" xsi:type="string">4</data> + <constraint name="Magento\Security\Test\Constraint\AssertUserIsLocked" /> + </variation> + </testCase> +</config> diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.php b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.php new file mode 100644 index 0000000000000000000000000000000000000000..912ebef9e91054236cc6b85b91c3cd8d973b54e9 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.php @@ -0,0 +1,133 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Security\Test\TestCase; + +use Magento\User\Test\Page\Adminhtml\UserEdit; +use Magento\User\Test\Page\Adminhtml\UserIndex; +use Magento\Backend\Test\Page\AdminAuthLogin; +use Magento\User\Test\Fixture\User; +use Magento\Mtf\TestCase\Injectable; + +/** + * Preconditions: + * 1. Create new admin user. + * 2. Configure 'Maximum Login Failures to Lockout Account'. + * + * Steps: + * 1. Log in to backend as new created admin user. + * 2. Navigate to System > All Users. + * 3. Start editing existing User. + * 4. Fill in all data according to data set (password is incorrect). + * 5. Perform action 4 specified number of times. + * 6. Admin account is locked. + * 7. Perform all assertions. + * + * @ZephyrId MAGETWO-49035 + */ +class LockAdminUserWhenEditingUserTest extends Injectable +{ + /* tags */ + const MVP = 'yes'; + const SEVERITY = 'S2'; + /* end tags */ + + /** + * User grid page + * + * @var UserIndex + */ + protected $userIndexPage; + + /** + * User edit page + * + * @var UserEdit + */ + protected $userEditPage; + + /** + * @var $configData + */ + protected $configData; + + /** + * @var AdminAuthLogin page + */ + protected $adminAuthLogin; + + /** + * Setup data for test. + * @param UserIndex $userIndex + * @param UserEdit $userEdit + * @param AdminAuthLogin $adminAuthLogin + */ + public function __inject( + UserIndex $userIndex, + UserEdit $userEdit, + AdminAuthLogin $adminAuthLogin + ) { + $this->userIndexPage = $userIndex; + $this->userEditPage = $userEdit; + $this->adminAuthLogin = $adminAuthLogin; + } + + /** + * Runs Lock admin user when editing existing role test. + * + * @param User $user + * @param int $attempts + * @param User $customAdmin + * @param string $configData + * @return void + */ + public function test( + $attempts, + User $customAdmin, + User $user, + $configData + ) { + $this->configData = $configData; + + // Preconditions + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData] + )->run(); + $customAdmin->persist(); + + // Steps login to backend with new user + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); + // Select user to edit. + $filter = ['username' => $customAdmin->getUsername()]; + $this->userIndexPage->open(); + $this->userIndexPage->getUserGrid()->searchAndOpen($filter); + // Edit user with wrong password + for ($i = 0; $i < $attempts; $i++) { + $this->userEditPage->getUserForm()->fill($user); + $this->userEditPage->getPageActions()->save(); + } + // Reload + $this->adminAuthLogin->open(); + $this->adminAuthLogin->getLoginBlock()->fill($customAdmin); + $this->adminAuthLogin->getLoginBlock()->submit(); + } + + /** + * Clean data after running test. + * + * @return void + */ + public function tearDown() + { + $this->objectManager->create( + \Magento\Config\Test\TestStep\SetupConfigurationStep::class, + ['configData' => $this->configData, 'rollback' => true] + )->run(); + } +} diff --git a/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.xml b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.xml new file mode 100644 index 0000000000000000000000000000000000000000..e1ec2f79ce6b5d306e694c9d2ae8e8adea0d3387 --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Security/Test/TestCase/LockAdminUserWhenEditingUserTest.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + --> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../../vendor/magento/mtf/etc/variations.xsd"> + <testCase name="Magento\Security\Test\TestCase\LockAdminUserWhenEditingUserTest" summary="Lock admin user after entering incorrect password while editing existing user"> + <variation name="LockAdminUserWhenEditingUseruserTestVariation1"> + <data name="configData" xsi:type="string">user_lockout_failures</data> + <data name="tag" xsi:type="string">severity:S2</data> + <data name="customAdmin/dataset" xsi:type="string">custom_admin_with_default_role</data> + <data name="user/data/username" xsi:type="string">AdminUser%isolation%</data> + <data name="user/data/firstname" xsi:type="string">FirstName%isolation%</data> + <data name="user/data/lastname" xsi:type="string">LastName%isolation%</data> + <data name="user/data/email" xsi:type="string">email%isolation%@example.com</data> + <data name="user/data/password" xsi:type="string">123123qq</data> + <data name="user/data/password_confirmation" xsi:type="string">123123qq</data> + <data name="user/data/current_password" xsi:type="string">incorrect password</data> + <data name="attempts" xsi:type="string">4</data> + <constraint name="Magento\Security\Test\Constraint\AssertUserIsLocked" /> + </variation> + </testCase> +</config> \ No newline at end of file diff --git a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php index 80df7e6f8c631722fb314525b2d6733e2ac605ad..95240b5dba0644d7f10d80447399d516a4742ac2 100644 --- a/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php +++ b/dev/tests/functional/tests/app/Magento/Ui/Test/Block/Adminhtml/DataGrid.php @@ -165,7 +165,7 @@ class DataGrid extends Grid */ protected function waitFilterToLoad() { - $this->getTemplateBlock()->waitForElementNotVisible($this->loader); + $this->getTemplateBlock()->waitLoader(); $browser = $this->_rootElement; $selector = $this->filterButton . ', ' . $this->resetButton; $browser->waitUntil( diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Repository/User.xml b/dev/tests/functional/tests/app/Magento/User/Test/Repository/User.xml index 25b812bf77665d09877835e676fc5d11fdd1434a..dbb7a4aeb53bb667bf22e99e0198d1b0e3cab25c 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/Repository/User.xml +++ b/dev/tests/functional/tests/app/Magento/User/Test/Repository/User.xml @@ -41,5 +41,9 @@ <field name="current_password" xsi:type="string">%current_password%</field> <field name="is_active" xsi:type="string">Active</field> </dataset> + + <dataset name="system_admin"> + <field name="current_password" xsi:type="string">123123q</field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/DeleteAdminUserEntityTest.php b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/DeleteAdminUserEntityTest.php index 57de56c7f56f7397282e1863ec43a3f42c6a9ec2..67ede3855e7a77b7da397a42c4de161289387b8f 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/DeleteAdminUserEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/DeleteAdminUserEntityTest.php @@ -101,11 +101,13 @@ class DeleteAdminUserEntityTest extends Injectable * * @param User $user * @param string $isDefaultUser + * @param User $systemAdmin * @return void */ public function testDeleteAdminUserEntity( User $user, - $isDefaultUser + $isDefaultUser, + User $systemAdmin = null ) { $filter = [ 'username' => $user->getUsername(), @@ -118,6 +120,7 @@ class DeleteAdminUserEntityTest extends Injectable } $this->userIndex->open(); $this->userIndex->getUserGrid()->searchAndOpen($filter); + $this->userEdit->getUserForm()->fill($systemAdmin); $this->userEdit->getPageActions()->delete(); $this->userEdit->getModalBlock()->acceptAlert(); } diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/DeleteAdminUserEntityTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/DeleteAdminUserEntityTest.xml index 782c3243eaa6be76578a9fc9f8b670fb6920c4da..fd10c53dfd53393dfcf122888a4d7b9e1003228a 100644 --- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/DeleteAdminUserEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/DeleteAdminUserEntityTest.xml @@ -9,11 +9,13 @@ <testCase name="Magento\User\Test\TestCase\DeleteAdminUserEntityTest" summary="Delete Admin User" ticketId="MAGETWO-23416"> <variation name="DeleteAdminUserEntityTestVariation1"> <data name="isDefaultUser" xsi:type="string">0</data> + <data name="systemAdmin/dataset" xsi:type="string">system_admin</data> <constraint name="Magento\User\Test\Constraint\AssertImpossibleDeleteYourOwnAccount" /> <constraint name="Magento\User\Test\Constraint\AssertUserInGrid" /> </variation> <variation name="DeleteAdminUserEntityTestVariation2"> <data name="isDefaultUser" xsi:type="string">1</data> + <data name="systemAdmin/dataset" xsi:type="string">system_admin</data> <constraint name="Magento\User\Test\Constraint\AssertUserSuccessDeleteMessage" /> <constraint name="Magento\User\Test\Constraint\AssertUserNotInGrid" /> </variation> diff --git a/dev/tests/functional/tests/app/Magento/Usps/Test/Repository/ConfigData.xml b/dev/tests/functional/tests/app/Magento/Usps/Test/Repository/ConfigData.xml index 85c6a6fd80eecc69cc945d96a908f40dcfb1a356..83b50205387d5da98401799e7486aba04368c355 100644 --- a/dev/tests/functional/tests/app/Magento/Usps/Test/Repository/ConfigData.xml +++ b/dev/tests/functional/tests/app/Magento/Usps/Test/Repository/ConfigData.xml @@ -54,7 +54,6 @@ <item name="value" xsi:type="number">1</item> </field> </dataset> - <dataset name="usps_rollback"> <field name="carriers/usps/active" xsi:type="array"> <item name="scope" xsi:type="string">carriers</item> @@ -63,5 +62,21 @@ <item name="value" xsi:type="number">0</item> </field> </dataset> + <dataset name="usps_container_sm_flat_rate_box"> + <field name="carriers/usps/container" xsi:type="array"> + <item name="scope" xsi:type="string">carriers</item> + <item name="scope_id" xsi:type="number">1</item> + <item name="label" xsi:type="string">Small Flat-Rate Box</item> + <item name="value" xsi:type="string">SM FLAT RATE BOX</item> + </field> + </dataset> + <dataset name="usps_container_sm_flat_rate_box_rollback"> + <field name="carriers/usps/container" xsi:type="array"> + <item name="scope" xsi:type="string">carriers</item> + <item name="scope_id" xsi:type="number">1</item> + <item name="label" xsi:type="string">Variable</item> + <item name="value" xsi:type="string">VARIABLE</item> + </field> + </dataset> </repository> </config> diff --git a/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml index 0bd219bab3bfff2103ea9f7a1ff2934ca8e1ad59..8081b485c27d5c0e803a051f9126bdf6599e6781 100644 --- a/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml +++ b/dev/tests/functional/tests/app/Magento/Usps/Test/TestCase/OnePageCheckoutTest.xml @@ -16,10 +16,10 @@ <data name="address/dataset" xsi:type="string">US_address_1</data> <data name="shippingAddress/dataset" xsi:type="string">US_address_1_without_email</data> <data name="shipping/shipping_service" xsi:type="string">United States Postal Service</data> - <data name="shipping/shipping_method" xsi:type="string">Priority Mail 1-Day</data> - <data name="cart/data/shipping_method" xsi:type="string">Priority Mail 1-Day</data> + <data name="shipping/shipping_method" xsi:type="string">Priority Mail 1-Day Small Flat Rate Box</data> + <data name="cart/data/shipping_method" xsi:type="string">Priority Mail 1-Day Small Flat Rate Box</data> <data name="payment/method" xsi:type="string">checkmo</data> - <data name="configData" xsi:type="string">checkmo, usps, shipping_origin_US_CA</data> + <data name="configData" xsi:type="string">checkmo, usps, shipping_origin_US_CA, usps_container_sm_flat_rate_box</data> <data name="tag" xsi:type="string">test_type:3rd_party_test, severity:S0</data> <constraint name="Magento\Checkout\Test\Constraint\AssertOrderSuccessPlacedMessage"/> <constraint name="Magento\Checkout\Test\Constraint\AssertMinicartEmpty" /> diff --git a/dev/tests/functional/tests/app/Magento/Vault/Test/TestStep/UseVaultPaymentTokenStep.php b/dev/tests/functional/tests/app/Magento/Vault/Test/TestStep/UseVaultPaymentTokenStep.php index ad693923955d4f439340970eb07ebb7c14789574..066dc7786d892619e84272c69b655cfd8a74f140 100644 --- a/dev/tests/functional/tests/app/Magento/Vault/Test/TestStep/UseVaultPaymentTokenStep.php +++ b/dev/tests/functional/tests/app/Magento/Vault/Test/TestStep/UseVaultPaymentTokenStep.php @@ -17,20 +17,20 @@ class UseVaultPaymentTokenStep implements TestStepInterface * @var OrderCreateIndex */ private $orderCreatePage; - + /** * @var array */ - private $payment; + private $vault; /** * @param OrderCreateIndex $orderCreateIndex - * @param array $payment + * @param array $vault */ - public function __construct(OrderCreateIndex $orderCreateIndex, array $payment) + public function __construct(OrderCreateIndex $orderCreateIndex, array $vault) { $this->orderCreatePage = $orderCreateIndex; - $this->payment = $payment; + $this->vault = $vault; } /** @@ -39,8 +39,7 @@ class UseVaultPaymentTokenStep implements TestStepInterface public function run() { $block = $this->orderCreatePage->getCreateBlock(); - $this->payment['method'] .= '_cc_vault'; - $block->selectPaymentMethod($this->payment); - $block->selectVaultToken('token_switcher_' . $this->payment['method']); + $block->selectPaymentMethod($this->vault); + $block->selectVaultToken('token_switcher_' . $this->vault['method']); } } diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist.php index 47f787da38b04264372015a52449f984820490e5..bda0780f553006bddc3f0ce164cb4f6f824ee75d 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist.php +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist.php @@ -99,6 +99,7 @@ class Wishlist extends Block public function clickUpdateWishlist() { $this->waitFormToLoad(); + $this->_rootElement->hover(); $this->_rootElement->find($this->updateButton)->click(); } diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/Product.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/Product.php index 076739590e1f19b86fee7bc105221f51efda34cb..683ff71e8ff7212e4e9604d581d04b4cf9b0f238 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/Product.php +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Block/Customer/Wishlist/Items/Product.php @@ -164,6 +164,18 @@ class Product extends Form $this->_rootElement->find($this->price)->hover(); } + /** + * Returns product price + * + * @param string $currency + * @return string + */ + public function getPrice($currency = '$') + { + $price = $this->_rootElement->find($this->price)->getText(); + return str_replace($currency, '', $price); + } + /** * Get Wish List data for the Product. * diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductPriceIsNotZero.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductPriceIsNotZero.php new file mode 100644 index 0000000000000000000000000000000000000000..24f6222dac09eb34cf3c3d6c5a063341b2acc2ef --- /dev/null +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/Constraint/AssertProductPriceIsNotZero.php @@ -0,0 +1,47 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Wishlist\Test\Constraint; + +class AssertProductPriceIsNotZero extends \Magento\Mtf\Constraint\AbstractConstraint +{ + /** + * Assert that product price is not zero in default wishlist. + * + * @param \Magento\Cms\Test\Page\CmsIndex $cmsIndex + * @param \Magento\Customer\Test\Page\CustomerAccountIndex $customerAccountIndex + * @param \Magento\Wishlist\Test\Page\WishlistIndex $wishlistIndex + * @param \Magento\Mtf\Fixture\InjectableFixture $product + * + * @return void + */ + public function processAssert( + \Magento\Cms\Test\Page\CmsIndex $cmsIndex, + \Magento\Customer\Test\Page\CustomerAccountIndex $customerAccountIndex, + \Magento\Wishlist\Test\Page\WishlistIndex $wishlistIndex, + \Magento\Mtf\Fixture\InjectableFixture $product + ) { + $cmsIndex->getLinksBlock()->openLink('My Account'); + $customerAccountIndex->getAccountMenuBlock()->openMenuItem('My Wish List'); + $wishlistItem = $wishlistIndex->getWishlistBlock()->getProductItemsBlock()->getItemProduct($product); + + \PHPUnit_Framework_Assert::assertNotEquals( + '0.00', + $wishlistItem->getPrice(), + $product->getName() . ' has zero price on Wish List page.' + ); + } + + /** + * Returns a string representation of the object. + * + * @return string + */ + public function toString() + { + return 'Product price is not zero in default Wish List.'; + } +} diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.php b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.php index 9959796828df23943db8fc3f6dfdce4b6387e1ac..bd922feee99d0315b5f7f16ee13b60eb101b626e 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.php +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.php @@ -48,15 +48,16 @@ class AddProductToWishlistEntityTest extends AbstractWishlistTest * * @param Customer $customer * @param string $product + * @param bool $configure * @return array */ - public function test(Customer $customer, $product) + public function test(Customer $customer, $product, $configure = true) { $product = $this->createProducts($product)[0]; // Steps: $this->loginCustomer($customer); - $this->addToWishlist([$product], true); + $this->addToWishlist([$product], $configure); return ['product' => $product]; } diff --git a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.xml b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.xml index 0557fa298ce985313593216b561b181175719a70..550cffa488130e16f73a9d89b9bc4344a57a2291 100644 --- a/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.xml +++ b/dev/tests/functional/tests/app/Magento/Wishlist/Test/TestCase/AddProductToWishlistEntityTest.xml @@ -49,5 +49,12 @@ <constraint name="Magento\Wishlist\Test\Constraint\AssertProductDetailsInWishlist" /> <constraint name="Magento\Wishlist\Test\Constraint\AssertProductIsPresentInCustomerBackendWishlist" /> </variation> + <variation name="AddProductToWishlistEntityTestVariation8"> + <data name="product/0" xsi:type="string">configurableProduct::default</data> + <data name="configure" xsi:type="boolean">false</data> + <constraint name="Magento\Wishlist\Test\Constraint\AssertAddProductToWishlistSuccessMessage" /> + <constraint name="Magento\Wishlist\Test\Constraint\AssertProductIsPresentInWishlist" /> + <constraint name="Magento\Wishlist\Test\Constraint\AssertProductPriceIsNotZero" /> + </variation> </testCase> </config> diff --git a/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/etc/module.xml b/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/etc/module.xml new file mode 100644 index 0000000000000000000000000000000000000000..ce5ed9cb663a759edf057291f11ccc6a75fe6266 --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/etc/module.xml @@ -0,0 +1,14 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_TestModuleDirectoryZipCodes" setup_version="0.0.1" active="true"> + <sequence> + <module name="Magento_Directory"/> + </sequence> + </module> +</config> diff --git a/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/etc/zip_codes.xml b/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/etc/zip_codes.xml new file mode 100644 index 0000000000000000000000000000000000000000..d4f121c60daeb8c44f327f8149f0148051c252ff --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/etc/zip_codes.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Directory:etc/zip_codes.xsd"> + <zip countryCode="NL"> + <codes> + <code id="pattern_1" active="true" example="test1">^[0-9]{4}\s[a-zA-Z]{2}$</code> + <code id="pattern_2" active="true" example="test2">^[0-5]{4}[a-z]{2}$</code> + </codes> + </zip> + <zip countryCode="NL_NEW"> + <codes> + <code id="pattern_1" active="true" example="test1">^[0-2]{4}[A-Z]{2}$</code> + </codes> + </zip> +</config> diff --git a/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/registration.php b/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/registration.php new file mode 100644 index 0000000000000000000000000000000000000000..2ed368ecf3b23a4bd4db86357f6cbbca0bfc55de --- /dev/null +++ b/dev/tests/integration/_files/Magento/TestModuleDirectoryZipCodes/registration.php @@ -0,0 +1,12 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Framework\Component\ComponentRegistrar; + +$registrar = new ComponentRegistrar(); +if ($registrar->getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleDirectoryZipCodes') === null) { + ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleDirectoryZipCodes', __DIR__); +} diff --git a/dev/tests/integration/_files/Magento/TestModuleSample/composer.json b/dev/tests/integration/_files/Magento/TestModuleSample/composer.json index 4d6383045d27f2123e84146eae970ec74ae77797..3e8e5650b85a77f75c079ae0ad9211a8d4d0764a 100644 --- a/dev/tests/integration/_files/Magento/TestModuleSample/composer.json +++ b/dev/tests/integration/_files/Magento/TestModuleSample/composer.json @@ -2,7 +2,7 @@ "name": "magento/module-sample-test", "description": "test sample module", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "100.1.*", "magento/module-integration": "100.1.*" }, diff --git a/dev/tests/integration/testsuite/Magento/Braintree/Model/Ui/Adminhtml/PayPal/TokenUiComponentProviderTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Model/Ui/Adminhtml/PayPal/TokenUiComponentProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e79f4ad361e36d315e7bc30846325bbe97445d1e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Braintree/Model/Ui/Adminhtml/PayPal/TokenUiComponentProviderTest.php @@ -0,0 +1,62 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Braintree\Model\Ui\Adminhtml\PayPal; + +use Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider; +use Magento\Braintree\Model\Ui\PayPal\ConfigProvider; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Vault\Model\PaymentTokenManagement; +use Magento\Vault\Model\Ui\TokenUiComponentProviderInterface; + +/** + * Contains tests for PayPal token Ui component provider + */ +class TokenUiComponentProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @var TokenUiComponentProvider + */ + private $tokenComponentProvider; + + protected function setUp() + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->tokenComponentProvider = $this->objectManager->get(TokenUiComponentProvider::class); + } + + /** + * @covers \Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider::getComponentForToken + * @magentoDataFixture Magento/Braintree/_files/paypal_vault_token.php + * @magentoAppArea adminhtml + */ + public function testGetComponentForToken() + { + $customerId = 1; + $token = 'mx29vk'; + $payerEmail = 'john.doe@example.com'; + + /** @var PaymentTokenManagement $tokenManagement */ + $tokenManagement = $this->objectManager->get(PaymentTokenManagement::class); + $paymentToken = $tokenManagement->getByGatewayToken($token, ConfigProvider::PAYPAL_CODE, $customerId); + + $component = $this->tokenComponentProvider->getComponentForToken($paymentToken); + $config = $component->getConfig(); + + static::assertNotEmpty($config[TokenUiComponentProviderInterface::COMPONENT_DETAILS]); + static::assertNotEmpty($config[TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH]); + static::assertEquals(ConfigProvider::PAYPAL_VAULT_CODE, $config['code']); + + $details = $config[TokenUiComponentProviderInterface::COMPONENT_DETAILS]; + static::assertEquals($payerEmail, $details['payerEmail']); + static::assertNotEmpty($details['icon']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php index a3e90819912f03a9413311600c7cd5cbcfddff60..49787c94038a1d9b1c3991c7f6261c0b7ef1453e 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php @@ -293,7 +293,7 @@ class CategoryTest extends \PHPUnit_Framework_TestCase ->isObjectNew(true); $repository->save($model); - $this->assertNull($model->getImage()); + $this->assertEmpty($model->getImage()); } /** diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php index 0afede93546f2c8ba69f8af9b64b70013b5d2c22..dc9e440b045f86ad2e85de45124ac01881d3892c 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php @@ -227,7 +227,34 @@ class ProductTest extends \PHPUnit_Framework_TestCase $this->assertContains('""Option 2""', $exportData); $this->assertContains('""Option 3""', $exportData); - $this->assertContains('""Option 4 """"!@#$%^&*"""', $exportData); - $this->assertContains('text_attribute=""!@#$%^&*()_+1234567890-=|\:;"""', $exportData); + $this->assertContains('""Option 4 """"!@#$%^&*""', $exportData); + $this->assertContains('text_attribute=""!@#$%^&*()_+1234567890-=|\:;""""\'<,>.?/', $exportData); + } + + /** + * Verify that "category ids" filter correctly applies to export result + * + * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_with_categories.php + */ + public function testCategoryIdsFilter() + { + $this->model->setWriter( + $this->objectManager->create( + \Magento\ImportExport\Model\Export\Adapter\Csv::class + ) + ); + + $this->model->setParameters([ + \Magento\ImportExport\Model\Export::FILTER_ELEMENT_GROUP => [ + 'category_ids' => '2,13' + ] + ]); + + $exportData = $this->model->export(); + + $this->assertContains('Simple Product', $exportData); + $this->assertContains('Simple Product Three', $exportData); + $this->assertNotContains('Simple Product Two', $exportData); + $this->assertNotContains('Simple Product Not Visible On Storefront', $exportData); } } 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 9ab7db147d811d370a4c2609d5baf5ea35225680..d8b2189e9f0d267d24d86ea77c67e84b2e2f73c9 100644 --- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php @@ -1431,4 +1431,113 @@ class ProductTest extends \Magento\TestFramework\Indexer\TestCase $this->assertEquals(implode(',', [$multiselectOptions[1]->getValue(), $multiselectOptions[2]->getValue()]), $product2->getData('multiselect_attribute')); } + + /** + * @param array $row + * @param string|null $behavior + * @param bool $expectedResult + * @magentoAppArea adminhtml + * @magentoAppIsolation enabled + * @magentoDbIsolation enabled + * @magentoDataFixture Magento/Catalog/Model/ResourceModel/_files/product_simple.php + * @dataProvider validateRowDataProvider + */ + public function testValidateRow(array $row, $behavior, $expectedResult) + { + $this->_model->setParameters(['behavior' => $behavior, 'entity' => 'catalog_product']); + $this->assertSame($expectedResult, $this->_model->validateRow($row, 1)); + } + + /** + * @return array + */ + public function validateRowDataProvider() + { + return [ + [ + 'row' => ['sku' => 'simple products'], + 'behavior' => null, + 'expectedResult' => true, + ], + [ + 'row' => ['sku' => 'simple products absent'], + 'behavior' => null, + 'expectedResult' => false, + ], + [ + 'row' => [ + 'sku' => 'simple products absent', + 'name' => 'Test', + 'product_type' => 'simple', + '_attribute_set' => 'Default', + 'price' => 10.20, + ], + 'behavior' => null, + 'expectedResult' => true, + ], + [ + 'row' => ['sku' => 'simple products'], + 'behavior' => Import::BEHAVIOR_ADD_UPDATE, + 'expectedResult' => true, + ], + [ + 'row' => ['sku' => 'simple products absent'], + 'behavior' => Import::BEHAVIOR_ADD_UPDATE, + 'expectedResult' => false, + ], + [ + 'row' => [ + 'sku' => 'simple products absent', + 'name' => 'Test', + 'product_type' => 'simple', + '_attribute_set' => 'Default', + 'price' => 10.20, + ], + 'behavior' => Import::BEHAVIOR_ADD_UPDATE, + 'expectedResult' => true, + ], + [ + 'row' => ['sku' => 'simple products'], + 'behavior' => Import::BEHAVIOR_DELETE, + 'expectedResult' => true, + ], + [ + 'row' => ['sku' => 'simple products absent'], + 'behavior' => Import::BEHAVIOR_DELETE, + 'expectedResult' => false, + ], + [ + 'row' => ['sku' => 'simple products'], + 'behavior' => Import::BEHAVIOR_REPLACE, + 'expectedResult' => false, + ], + [ + 'row' => ['sku' => 'simple products absent'], + 'behavior' => Import::BEHAVIOR_REPLACE, + 'expectedResult' => false, + ], + [ + 'row' => [ + 'sku' => 'simple products absent', + 'name' => 'Test', + 'product_type' => 'simple', + '_attribute_set' => 'Default', + 'price' => 10.20, + ], + 'behavior' => Import::BEHAVIOR_REPLACE, + 'expectedResult' => false, + ], + [ + 'row' => [ + 'sku' => 'simple products', + 'name' => 'Test', + 'product_type' => 'simple', + '_attribute_set' => 'Default', + 'price' => 10.20, + ], + 'behavior' => Import::BEHAVIOR_REPLACE, + 'expectedResult' => true, + ], + ]; + } } diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_categories.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_categories.php new file mode 100644 index 0000000000000000000000000000000000000000..e65ff00bcffcabd1e2fdeae3810876178e0d06aa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_categories.php @@ -0,0 +1,9 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); + +require dirname(dirname(__DIR__)) . '/Catalog/_files/categories.php'; diff --git a/dev/tests/integration/testsuite/Magento/CatalogInventory/Api/StockItemSaveTest.php b/dev/tests/integration/testsuite/Magento/CatalogInventory/Api/StockItemSaveTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0dc5c3ef620c3c307d4b35b6b7b0e61091128775 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/CatalogInventory/Api/StockItemSaveTest.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\CatalogInventory\Api; + +use Magento\Catalog\Api\Data\ProductExtensionInterface; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductRepositoryInterface; + +class StockItemSaveTest extends \PHPUnit_Framework_TestCase +{ + /** + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testSave() + { + $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + /** @var ProductRepositoryInterface $productRepository */ + $productRepository = $objectManager->get(ProductRepositoryInterface::class); + /** @var ProductInterface $product */ + $product = $productRepository->get('simple', false, null, true); + + /** @var ProductExtensionInterface $ea */ + $ea = $product->getExtensionAttributes(); + $ea->getStockItem()->setQty(555); + $productRepository->save($product); + + $product = $productRepository->get('simple', false, null, true); + $this->assertEquals(555, $product->getExtensionAttributes()->getStockItem()->getQty()); + + $stockItem = $product->getExtensionAttributes()->getStockItem(); + $stockItem->setQty(200); + /** @var StockItemRepositoryInterface $stockItemRepository */ + $stockItemRepository = $objectManager->get(StockItemRepositoryInterface::class); + $stockItemRepository->save($stockItem); + $this->assertEquals(200, $product->getExtensionAttributes()->getStockItem()->getQty()); + + $product = $productRepository->get('simple', false, null, true); + $this->assertEquals(200, $product->getExtensionAttributes()->getStockItem()->getQty()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php index 34a484f749b7807a7096e22fc24c21b8b6e6c5dd..54a068dad5d0a26f1564d662eb962eca64527110 100644 --- a/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php +++ b/dev/tests/integration/testsuite/Magento/Checkout/Controller/CartTest.php @@ -229,4 +229,51 @@ class CartTest extends \Magento\TestFramework\TestCase\AbstractController } return null; } + + /** + * Test for \Magento\Checkout\Controller\Cart::execute() with simple product + * + * @param string $area + * @param string $expectedPrice + * @magentoDataFixture Magento/Catalog/_files/products.php + * @magentoAppIsolation enabled + * @dataProvider addAddProductDataProvider + */ + public function testAddToCartSimpleProduct($area, $expectedPrice) + { + $formKey = $this->_objectManager->get(\Magento\Framework\Data\Form\FormKey::class); + $postData = [ + 'qty' => '1', + 'product' => '1', + 'custom_price' => 1, + 'form_key' => $formKey->getFormKey(), + 'isAjax' => 1 + ]; + \Magento\TestFramework\Helper\Bootstrap::getInstance()->loadArea($area); + $this->getRequest()->setPostValue($postData); + + $quote = $this->_objectManager->create(\Magento\Checkout\Model\Cart::class); + /** @var \Magento\Checkout\Controller\Cart\Add $controller */ + $controller = $this->_objectManager->create(\Magento\Checkout\Controller\Cart\Add::class, [$quote]); + $controller->execute(); + + $this->assertContains(json_encode([]), $this->getResponse()->getBody()); + $items = $quote->getItems()->getItems(); + $this->assertTrue(is_array($items), 'Quote doesn\'t have any items'); + $this->assertCount(1, $items, 'Expected quote items not equal to 1'); + $item = reset($items); + $this->assertEquals(1, $item->getProductId(), 'Quote has more than one product'); + $this->assertEquals($expectedPrice, $item->getPrice(), 'Expected product price failed'); + } + + /** + * Data provider for testAddToCartSimpleProduct + */ + public function addAddProductDataProvider() + { + return [ + 'frontend' => ['frontend', 'expected_price' => 10], + 'adminhtml' => ['adminhtml', 'expected_price' => 1] + ]; + } } diff --git a/dev/tests/integration/testsuite/Magento/Config/Model/Config/Backend/BaseurlTest.php b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Backend/BaseurlTest.php index 0ce30e5b2984120c7e325a151d96c0ef044affa6..5a2db8ccb22fa0484ab71c04f8a6bbc4cbc7b0e6 100644 --- a/dev/tests/integration/testsuite/Magento/Config/Model/Config/Backend/BaseurlTest.php +++ b/dev/tests/integration/testsuite/Magento/Config/Model/Config/Backend/BaseurlTest.php @@ -95,33 +95,44 @@ class BaseurlTest extends \PHPUnit_Framework_TestCase $unsecurePlaceholder = '{{unsecure_base_url}}'; $unsecureSuffix = '{{unsecure_base_url}}test/'; $unsecureWrongSuffix = '{{unsecure_base_url}}test'; + $unsecureWrongDomainName = 'http://example.com_test/'; $securePlaceholder = '{{secure_base_url}}'; $secureSuffix = '{{secure_base_url}}test/'; $secureWrongSuffix = '{{secure_base_url}}test'; + $secureWrongDomainName = 'https://example.com_test/'; return [ ['', 'not a valid URL'], ['', 'example.com'], ['', 'http://example.com'], ['', 'http://example.com/uri'], + ['', $unsecureWrongDomainName], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_URL, ''], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_URL, $baseSuffix], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_URL, $unsecureSuffix], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_URL, $unsecurePlaceholder], + [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_URL, $unsecureWrongDomainName], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_LINK_URL, ''], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_LINK_URL, $baseSuffix], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_LINK_URL, $unsecureWrongSuffix], + [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_LINK_URL, $unsecureWrongDomainName], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_MEDIA_URL, $unsecureWrongSuffix], + [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_MEDIA_URL, $unsecureWrongDomainName], [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_STATIC_URL, $unsecureWrongSuffix], + [\Magento\Store\Model\Store::XML_PATH_UNSECURE_BASE_STATIC_URL, $unsecureWrongDomainName], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_URL, ''], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_URL, $baseSuffix], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_URL, $secureSuffix], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_URL, $securePlaceholder], + [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_URL, $secureWrongDomainName], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_LINK_URL, ''], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_LINK_URL, $baseSuffix], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_LINK_URL, $secureWrongSuffix], + [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_LINK_URL, $secureWrongDomainName], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_MEDIA_URL, $secureWrongSuffix], + [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_MEDIA_URL, $secureWrongDomainName], [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_STATIC_URL, $secureWrongSuffix], + [\Magento\Store\Model\Store::XML_PATH_SECURE_BASE_STATIC_URL, $secureWrongDomainName], ]; } } diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5db97cc36614924ae915b7b924b2c7a1f575e344 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/ConfigurableTest.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\ResourceModel\Product\Collection; +use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +/** + * @magentoAppArea adminhtml + */ +class ConfigurableTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + protected function setUp() + { + $this->storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); + $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + */ + public function testGetProductFinalPriceIfOneOfChildIsDisabled() + { + /** @var Collection $collection */ + $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) + ->create(); + $configurableProduct = $collection + ->addIdFilter([1]) + ->addMinimalPrice() + ->load() + ->getFirstItem(); + $this->assertEquals(10, $configurableProduct->getMinimalPrice()); + + $childProduct = $this->productRepository->getById(10, false, null, true); + $childProduct->setStatus(Status::STATUS_DISABLED); + // update in global scope + $currentStoreId = $this->storeManager->getStore()->getId(); + $this->storeManager->setCurrentStore(Store::ADMIN_CODE); + $this->productRepository->save($childProduct); + $this->storeManager->setCurrentStore($currentStoreId); + + /** @var Collection $collection */ + $collection = Bootstrap::getObjectManager()->get(CollectionFactory::class) + ->create(); + $configurableProduct = $collection + ->addIdFilter([1]) + ->addMinimalPrice() + ->load() + ->getFirstItem(); + $this->assertEquals(20, $configurableProduct->getMinimalPrice()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..50787c7962412b0611edf093825563061da67119 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Pricing/Price/LowestPriceOptionProviderTest.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\ConfigurableProduct\Pricing\Price; + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; + +class LowestPriceOptionProviderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var LowestPriceOptionsProviderInterface + */ + private $lowestPriceOptionsProvider; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + protected function setUp() + { + $this->storeManager = Bootstrap::getObjectManager()->get(StoreManagerInterface::class); + $this->productRepository = Bootstrap::getObjectManager()->get(ProductRepositoryInterface::class); + $this->lowestPriceOptionsProvider = Bootstrap::getObjectManager()->get( + LowestPriceOptionsProviderInterface::class + ); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + */ + public function testGetProductsIfOneOfChildIsDisabled() + { + $configurableProduct = $this->productRepository->getById(1, false, null, true); + $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct); + $this->assertCount(1, $lowestPriceChildrenProducts); + $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts); + $this->assertEquals(10, $lowestPriceChildrenProduct->getPrice()); + + // load full aggregation root + $lowestPriceChildProduct = $this->productRepository->get( + $lowestPriceChildrenProduct->getSku(), + false, + null, + true + ); + $lowestPriceChildProduct->setStatus(Status::STATUS_DISABLED); + // update in global scope + $currentStoreId = $this->storeManager->getStore()->getId(); + $this->storeManager->setCurrentStore(Store::ADMIN_CODE); + $this->productRepository->save($lowestPriceChildProduct); + $this->storeManager->setCurrentStore($currentStoreId); + + $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct); + $this->assertCount(1, $lowestPriceChildrenProducts); + $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts); + $this->assertEquals(20, $lowestPriceChildrenProduct->getPrice()); + } + + /** + * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php + */ + public function testGetProductsIfOneOfChildIsOutOfStock() + { + $configurableProduct = $this->productRepository->getById(1, false, null, true); + $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct); + $this->assertCount(1, $lowestPriceChildrenProducts); + $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts); + $this->assertEquals(10, $lowestPriceChildrenProduct->getPrice()); + + // load full aggregation root + $lowestPriceChildProduct = $this->productRepository->get( + $lowestPriceChildrenProduct->getSku(), + false, + null, + true + ); + $stockItem = $lowestPriceChildProduct->getExtensionAttributes()->getStockItem(); + $stockItem->setIsInStock(0); + $this->productRepository->save($lowestPriceChildProduct); + + $lowestPriceChildrenProducts = $this->lowestPriceOptionsProvider->getProducts($configurableProduct); + $this->assertCount(1, $lowestPriceChildrenProducts); + $lowestPriceChildrenProduct = reset($lowestPriceChildrenProducts); + $this->assertEquals(20, $lowestPriceChildrenProduct->getPrice()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php index ff08056e4b18def3f520d2a5d9f2090b168d5ec8..0d8361b4751e38a96ae4dbb7aadb86510dad92dc 100644 --- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php +++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/_files/product_simple_77.php @@ -1,5 +1,7 @@ <?php /** + * Creates a simple product to be used for test cases. + * * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ @@ -72,7 +74,8 @@ $product->setTypeId(Type::TYPE_SIMPLE) 'is_in_stock' => 1, ] )->setCanSaveCustomOptions(true) - ->setHasOptions(true); + ->setHasOptions(true) + ->setCustomAttribute('test_configurable', 42); $oldOptions = [ [ diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php index 1576ff90cb38ee4cd89b68766395fa0746d071d6..5f9b9e928b3178f3ae06a8d5a885022cc8e954cc 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Adminhtml/IndexTest.php @@ -313,6 +313,8 @@ class IndexTest extends \Magento\TestFramework\TestCase\AbstractBackendControlle $this->assertEquals(2, count($addresses)); $updatedAddress = $this->addressRepository->getById(1); $this->assertEquals('update firstname', $updatedAddress->getFirstname()); + $this->assertTrue($updatedAddress->isDefaultBilling()); + $this->assertEquals($updatedAddress->getId(), $customer->getDefaultBilling()); $newAddress = $this->accountManagement->getDefaultShippingAddress($customerId); $this->assertEquals('new firstname', $newAddress->getFirstname()); diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/Section/LoadTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/Section/LoadTest.php new file mode 100644 index 0000000000000000000000000000000000000000..c44e320e64f4d0cfe1646f97642a2080e604ed74 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/Section/LoadTest.php @@ -0,0 +1,19 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Customer\Controller\Section; + +class LoadTest extends \Magento\TestFramework\TestCase\AbstractController +{ + public function testLoadInvalidSection() + { + $expected = [ + 'message' => '"section<invalid" section source is not supported', + ]; + $this->dispatch('/customer/section/load/?sections=section<invalid&update_section_id=false&_=147066166394'); + self::assertEquals(json_encode($expected), $this->getResponse()->getBody()); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/Config/ReaderTest.php b/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/Config/ReaderTest.php new file mode 100644 index 0000000000000000000000000000000000000000..2b57fcb43a1a4326fc80dadcbc8211f6ed35b623 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Directory/Model/Country/Postcode/Config/ReaderTest.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Directory\Model\Country\Postcode\Config; + +class ReaderTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Directory\Model\Country\Postcode\Config\Reader + */ + private $reader; + + protected function setUp() + { + $this->reader = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Directory\Model\Country\Postcode\Config\Reader::class + ); + } + + public function testRead() + { + $result = $this->reader->read(); + + $this->assertArrayHasKey('NL', $result); + $this->assertArrayHasKey('pattern_1', $result['NL']); + $this->assertArrayHasKey('pattern_2', $result['NL']); + + $this->assertEquals('test1', $result['NL']['pattern_1']['example']); + $this->assertEquals('^[0-9]{4}\s[a-zA-Z]{2}$', $result['NL']['pattern_1']['pattern']); + + $this->assertEquals('test2', $result['NL']['pattern_2']['example']); + $this->assertEquals('^[0-5]{4}[a-z]{2}$', $result['NL']['pattern_2']['pattern']); + + $this->assertArrayHasKey('NL_NEW', $result); + $this->assertArrayHasKey('pattern_1', $result['NL_NEW']); + + $this->assertEquals('test1', $result['NL_NEW']['pattern_1']['example']); + $this->assertEquals('^[0-2]{4}[A-Z]{2}$', $result['NL_NEW']['pattern_1']['pattern']); + } +} diff --git a/dev/tests/integration/testsuite/Magento/Framework/DB/Adapter/Pdo/MysqlTest.php b/dev/tests/integration/testsuite/Magento/Framework/DB/Adapter/Pdo/MysqlTest.php index 8a566a0d4efe55e856716c095f5a3a7e14896af1..14118414859f0d9bced34e393bfb699f22251edf 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/DB/Adapter/Pdo/MysqlTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/DB/Adapter/Pdo/MysqlTest.php @@ -12,6 +12,7 @@ namespace Magento\Framework\DB\Adapter\Pdo; use Magento\Framework\App\ResourceConnection; +use Zend_Db_Statement_Exception; class MysqlTest extends \PHPUnit_Framework_TestCase { @@ -32,6 +33,7 @@ class MysqlTest extends \PHPUnit_Framework_TestCase restore_error_handler(); } + /** * Test lost connection re-initializing * @@ -125,9 +127,9 @@ class MysqlTest extends \PHPUnit_Framework_TestCase protected function _getConnection() { if (is_null($this->_connection)) { - /** @var $coreResource \Magento\Framework\App\ResourceConnection */ + /** @var $coreResource ResourceConnection */ $coreResource = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() - ->get(\Magento\Framework\App\ResourceConnection::class); + ->get(ResourceConnection::class); $this->_connection = $coreResource->getConnection(); } return $this->_connection; diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php index 9acfa6f1caab150b0a0db2d54e87358adf88cf5b..67a6e416973493887912e2e62674108b1694c315 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php @@ -414,6 +414,37 @@ class AdapterTest extends \PHPUnit_Framework_TestCase $this->assertEquals($expectedRecordsCount, $queryResponse->count()); } + /** + * @magentoDataFixture Magento/Framework/Search/_files/product_configurable.php + * @magentoConfigFixture current_store catalog/search/engine mysql + */ + public function testAdvancedSearchCompositeProductWithOutOfStockOption() + { + /** @var \Magento\Catalog\Model\ResourceModel\Eav\Attribute $attribute */ + $attribute = $this->objectManager->get(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class) + ->loadByCode(\Magento\Catalog\Model\Product::ENTITY, 'test_configurable'); + /** @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection $selectOptions */ + $selectOptions = $this->objectManager + ->create(\Magento\Eav\Model\ResourceModel\Entity\Attribute\Option\Collection::class) + ->setAttributeFilter($attribute->getId()); + + $firstOption = $selectOptions->getFirstItem(); + $firstOptionId = $firstOption->getId(); + $this->requestBuilder->bind('test_configurable', $firstOptionId); + $this->requestBuilder->setRequestName('filter_out_of_stock_child'); + + $queryResponse = $this->executeQuery(); + $this->assertEquals(0, $queryResponse->count()); + + $secondOption = $selectOptions->getLastItem(); + $secondOptionId = $secondOption->getId(); + $this->requestBuilder->bind('test_configurable', $secondOptionId); + $this->requestBuilder->setRequestName('filter_out_of_stock_child'); + + $queryResponse = $this->executeQuery(); + $this->assertEquals(1, $queryResponse->count()); + } + public function dateDataProvider() { return [ diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute.php new file mode 100644 index 0000000000000000000000000000000000000000..030e0250c3ec7fed4747aed4e168413207c7daf7 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute.php @@ -0,0 +1,61 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\TestFramework\Helper\Bootstrap; +use Magento\Eav\Api\AttributeRepositoryInterface; + +$eavConfig = Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); + +$eavConfig->clear(); + +/** @var $installer \Magento\Catalog\Setup\CategorySetup */ +$installer = Bootstrap::getObjectManager()->create(\Magento\Catalog\Setup\CategorySetup::class); + +if (!$attribute->getId()) { + + /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */ + $attribute = Bootstrap::getObjectManager()->create( + \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class + ); + + /** @var AttributeRepositoryInterface $attributeRepository */ + $attributeRepository = Bootstrap::getObjectManager()->create(AttributeRepositoryInterface::class); + + $attribute->setData( + [ + 'attribute_code' => 'test_configurable', + 'entity_type_id' => $installer->getEntityTypeId('catalog_product'), + 'is_global' => 1, + 'is_user_defined' => 1, + 'frontend_input' => 'select', + 'is_unique' => 0, + 'is_required' => 0, + 'is_searchable' => 1, + 'is_visible_in_advanced_search' => 1, + 'is_comparable' => 0, + 'is_filterable' => 1, + 'is_filterable_in_search' => 1, + 'is_used_for_promo_rules' => 0, + 'is_html_allowed_on_front' => 1, + 'is_visible_on_front' => 0, + 'used_in_product_listing' => 0, + 'used_for_sort_by' => 0, + 'frontend_label' => ['Test Configurable'], + 'backend_type' => 'int', + 'option' => [ + 'value' => ['option_0' => ['Option 1'], 'option_1' => ['Option 2']], + 'order' => ['option_0' => 1, 'option_1' => 2], + ], + ] + ); + + $attributeRepository->save($attribute); +} + +/* Assign attribute to attribute set */ +$installer->addAttributeToGroup('catalog_product', 'Default', 'General', $attribute->getId()); +$eavConfig->clear(); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..bd18100f6d97b4b9b56c9743e8064e0723fdc6c8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/configurable_attribute_rollback.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +/** @var \Magento\Framework\Registry $registry */ +$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +$productCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Model\ResourceModel\Product\Collection::class); +foreach ($productCollection as $product) { + $product->delete(); +} + +$eavConfig = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Eav\Model\Config::class); +$attribute = $eavConfig->getAttribute('catalog_product', 'test_configurable'); +if ($attribute instanceof \Magento\Eav\Model\Entity\Attribute\AbstractAttribute + && $attribute->getId() +) { + $attribute->delete(); +} +$eavConfig->clear(); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php new file mode 100644 index 0000000000000000000000000000000000000000..590f3c8041c6d819fafa14ef7ddc4d1f2ebccbeb --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable.php @@ -0,0 +1,147 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Type; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Catalog\Setup\CategorySetup; +use Magento\ConfigurableProduct\Helper\Product\Options\Factory; +use Magento\ConfigurableProduct\Model\Product\Type\Configurable; +use Magento\Eav\Api\Data\AttributeOptionInterface; +use Magento\TestFramework\Helper\Bootstrap; + +\Magento\TestFramework\Helper\Bootstrap::getInstance()->reinitialize(); + +require __DIR__ . '/configurable_attribute.php'; + +/** @var ProductRepositoryInterface $productRepository */ +$productRepository = Bootstrap::getObjectManager() + ->create(ProductRepositoryInterface::class); + +/** @var $installer CategorySetup */ +$installer = Bootstrap::getObjectManager()->create(CategorySetup::class); + +/* Create simple products per each option value*/ +/** @var AttributeOptionInterface[] $options */ +$options = $attribute->getOptions(); + +$attributeValues = []; +$attributeSetId = $installer->getAttributeSetId('catalog_product', 'Default'); +$associatedProductIds = []; +$productIds = [10, 20]; +array_shift($options); //remove the first option which is empty + +$isFirstOption = true; +foreach ($options as $option) { + /** @var $product Product */ + $product = Bootstrap::getObjectManager()->create(Product::class); + $productId = array_shift($productIds); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setId($productId) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Option' . $option->getLabel()) + ->setSku('simple_' . $productId) + ->setPrice($productId) + ->setTestConfigurable($option->getValue()) + ->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData( + [ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => (int)!$isFirstOption, + ] + ); + + $product = $productRepository->save($product); + + /** @var \Magento\CatalogInventory\Model\Stock\Item $stockItem */ + $stockItem = Bootstrap::getObjectManager()->create(\Magento\CatalogInventory\Model\Stock\Item::class); + $stockItem->load($productId, 'product_id'); + + if (!$stockItem->getProductId()) { + $stockItem->setProductId($productId); + } + $stockItem->setUseConfigManageStock(1); + $stockItem->setQty(1000); + $stockItem->setIsQtyDecimal(0); + $stockItem->setIsInStock((int)!$isFirstOption); + $stockItem->save(); + + $attributeValues[] = [ + 'label' => 'test', + 'attribute_id' => $attribute->getId(), + 'value_index' => $option->getValue(), + ]; + $associatedProductIds[] = $product->getId(); + $isFirstOption = false; +} + +/** @var $product Product */ +$product = Bootstrap::getObjectManager()->create(Product::class); + +/** @var Factory $optionsFactory */ +$optionsFactory = Bootstrap::getObjectManager()->create(Factory::class); + +$configurableAttributesData = [ + [ + 'attribute_id' => $attribute->getId(), + 'code' => $attribute->getAttributeCode(), + 'label' => $attribute->getStoreLabel(), + 'position' => '0', + 'values' => $attributeValues, + ], +]; + +$configurableOptions = $optionsFactory->create($configurableAttributesData); + +$extensionConfigurableAttributes = $product->getExtensionAttributes(); +$extensionConfigurableAttributes->setConfigurableProductOptions($configurableOptions); +$extensionConfigurableAttributes->setConfigurableProductLinks($associatedProductIds); + +$product->setExtensionAttributes($extensionConfigurableAttributes); + +// Remove any previously created product with the same id. +/** @var \Magento\Framework\Registry $registry */ +$registry = Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +try { + $productToDelete = $productRepository->getById(1); + $productRepository->delete($productToDelete); + + /** @var \Magento\Quote\Model\ResourceModel\Quote\Item $itemResource */ + $itemResource = Bootstrap::getObjectManager()->get(\Magento\Quote\Model\ResourceModel\Quote\Item::class); + $itemResource->getConnection()->delete( + $itemResource->getMainTable(), + 'product_id = ' . $productToDelete->getId() + ); +} catch (\Exception $e) { + // Nothing to remove +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); + +$product->setTypeId(Configurable::TYPE_CODE) + ->setId(1) + ->setAttributeSetId($attributeSetId) + ->setWebsiteIds([1]) + ->setName('Configurable Product') + ->setSku('configurable') + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData(['use_config_manage_stock' => 1, 'is_in_stock' => 1]); + +$productRepository->save($product); +// +///** @var \Magento\Catalog\Model\Indexer\Product\Eav\Processor $eavIndexer */ +//$eavIndexer = Bootstrap::getObjectManager() +// ->get(\Magento\Catalog\Model\Indexer\Product\Eav\Processor::class); +//$eavIndexer->reindexAll(); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable_rollback.php new file mode 100644 index 0000000000000000000000000000000000000000..8ba4e3abe21cc14a75a66270d22ea262dffeb788 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/product_configurable_rollback.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + +/** @var \Magento\Framework\Registry $registry */ +$registry = $objectManager->get(\Magento\Framework\Registry::class); + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */ +$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager() + ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class); + +foreach (['simple_10', 'simple_20', 'configurable'] as $sku) { + try { + $product = $productRepository->get($sku, false, null, true); + + $stockStatus = $objectManager->create(\Magento\CatalogInventory\Model\Stock\Status::class); + $stockStatus->load($product->getEntityId(), 'product_id'); + $stockStatus->delete(); + + $productRepository->delete($product); + } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { + //Product already removed + } +} + +require __DIR__ . '/configurable_attribute_rollback.php'; + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml index 0cdfa11c3b6b5744abbd911e063553cd23039cc5..4cdc9a93e1f1f0ee093b8e0fd8e555a157fce851 100644 --- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml +++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml @@ -387,4 +387,24 @@ <from>0</from> <size>10</size> </request> + <request query="filter_out_of_stock_child" index="catalogsearch_fulltext"> + <dimensions> + <dimension name="scope" value="default"/> + </dimensions> + <queries> + <query xsi:type="boolQuery" name="filter_out_of_stock_child" boost="1"> + <queryReference clause="must" ref="test_configurable"/> + </query> + <query xsi:type="filteredQuery" name="test_configurable"> + <filterReference clause="must" ref="test_configurable_filter"/> + </query> + </queries> + <filters> + <filter xsi:type="termFilter" name="test_configurable_filter" field="test_configurable" value="$test_configurable$"/> + </filters> + <aggregations/> + <from>0</from> + <size>10</size> + </request> + </requests> diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/LayoutDirectivesTest.php b/dev/tests/integration/testsuite/Magento/Framework/View/LayoutDirectivesTest.php index 7a20762bbe9d4493131bb0f2acb8e432a0a9ddbf..607ee4c919a62437cdfaf0d2219248dfc77f7a27 100755 --- a/dev/tests/integration/testsuite/Magento/Framework/View/LayoutDirectivesTest.php +++ b/dev/tests/integration/testsuite/Magento/Framework/View/LayoutDirectivesTest.php @@ -198,6 +198,18 @@ class LayoutDirectivesTest extends \PHPUnit_Framework_TestCase $this->assertTrue($layout->isBlock('child_block2')); } + /** + * @magentoAppIsolation enabled + */ + public function testRemoveCancellation() + { + $layout = $this->_getLayoutModel('remove_cancellation.xml'); + $this->assertTrue($layout->isContainer('container1')); + $this->assertTrue($layout->isBlock('child_block1')); + $this->assertTrue($layout->isBlock('no_name2')); + $this->assertFalse($layout->getBlock('not_exist')); + } + /** * @magentoAppIsolation enabled */ diff --git a/dev/tests/integration/testsuite/Magento/Framework/View/_files/layout_directives_test/remove_cancellation.xml b/dev/tests/integration/testsuite/Magento/Framework/View/_files/layout_directives_test/remove_cancellation.xml new file mode 100644 index 0000000000000000000000000000000000000000..ff086b19fd1b02d80ffc849d9563bdb9419ae75e --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Framework/View/_files/layout_directives_test/remove_cancellation.xml @@ -0,0 +1,21 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<layout> + <container name="container1" label="Container 1"> + <block class="Magento\Framework\View\Element\Text" name="no_name2"/> + </container> + <referenceContainer name="container1" remove="true"/> + <referenceBlock name="child_block1" remove="true"/> + <block class="Magento\Framework\View\Element\Text" name="block_container" as="block.container"> + <block class="Magento\Framework\View\Element\Text" name="child_block1"/> + <block class="Magento\Framework\View\Element\Text" name="child_block2"/> + </block> + <referenceContainer name="not_exist" remove="false"/> + <referenceContainer name="container1" remove="false"/> + <referenceBlock name="child_block1" remove="false"/> +</layout> diff --git a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml index f9f8f1ffba91a55536c9d2221b68dc85b702e7f0..19feb33748c840b6c87f506ffd450897e2ace286 100644 --- a/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml +++ b/dev/tests/integration/testsuite/Magento/Paypal/Model/Config/Structure/Reader/_files/expected/config.xml @@ -593,7 +593,8 @@ <label>Express Checkout</label> <frontend_model>Magento\Paypal\Block\Adminhtml\System\Config\Fieldset\Expanded</frontend_model> <field id="business_account" translate="label comment tooltip" showInDefault="1" showInWebsite="1" sortOrder="5"> - <label>Email Associated with PayPal Merchant Account</label> + <label>Email Associated with PayPal Merchant Account (Optional)</label> + <frontend_class>not-required</frontend_class> <comment> <![CDATA[<a href="http://www.magentocommerce.com/paypal">Start accepting payments via PayPal!</a>]]> </comment> diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php index da035c8eec78f8a547defd548f5cc93b10d474a6..8f295077afe330076e265c555cae280294cdd9f6 100644 --- a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteTest.php @@ -404,4 +404,23 @@ class QuoteTest extends \PHPUnit_Framework_TestCase \Magento\Customer\Model\Data\Customer::WEBSITE_ID => 1 ]; } + + /** + * Test to verify that reserved_order_id will be changed if it already in used + * + * @magentoDataFixture Magento/Sales/_files/order.php + * @magentoDataFixture Magento/Quote/_files/empty_quote.php + */ + public function testReserveOrderId() + { + $objectManager = Bootstrap::getObjectManager(); + /** @var \Magento\Quote\Model\Quote $quote */ + $quote = $objectManager->create(\Magento\Quote\Model\Quote::class); + $quote->load('reserved_order_id', 'reserved_order_id'); + $quote->reserveOrderId(); + $this->assertEquals('reserved_order_id', $quote->getReservedOrderId()); + $quote->setReservedOrderId('100000001'); + $quote->reserveOrderId(); + $this->assertNotEquals('100000001', $quote->getReservedOrderId()); + } } diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..43ee0893b75f9cc93bdca626d87d5592eefd62aa --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Quote/Model/ResourceModel/QuoteTest.php @@ -0,0 +1,45 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Quote\Model\ResourceModel; + +/** + * Class QuoteTest to verify isOrderIncrementIdUsed method behaviour + */ +class QuoteTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Quote\Model\ResourceModel\Quote + */ + private $_resourceModel; + + protected function setUp() + { + $this->_resourceModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Quote\Model\ResourceModel\Quote::class + ); + } + + /** + * Test to verify if isOrderIncrementIdUsed method works with numeric increment ids + * + * @magentoDataFixture Magento/Sales/_files/order.php + */ + public function testIsOrderIncrementIdUsedNumericIncrementId() + { + $this->assertTrue($this->_resourceModel->isOrderIncrementIdUsed('100000001')); + } + + /** + * Test to verify if isOrderIncrementIdUsed method works with alphanumeric increment ids + * + * @magentoDataFixture Magento/Sales/_files/order_alphanumeric_id.php + */ + public function testIsOrderIncrementIdUsedAlphanumericIncrementId() + { + $this->assertTrue($this->_resourceModel->isOrderIncrementIdUsed('M00000001')); + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/Model/Quote/Address/Total/ShippingTest.php b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Quote/Address/Total/ShippingTest.php new file mode 100644 index 0000000000000000000000000000000000000000..543d02e42067483a56ee1b31d084a7e02c18952f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/Model/Quote/Address/Total/ShippingTest.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\SalesRule\Model\Quote\Address\Total; + +class ShippingTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var \Magento\Quote\Api\GuestCartManagementInterface + */ + private $cartManagement; + + /** + * @var \Magento\Quote\Api\GuestCartItemRepositoryInterface + */ + private $itemRepository; + + /** + * @var \Magento\Framework\ObjectManagerInterface + */ + private $objectManager; + + protected function setUp() + { + $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager(); + $this->cartManagement = $this->objectManager->get(\Magento\Quote\Api\GuestCartManagementInterface::class); + $this->itemRepository = $this->objectManager->get(\Magento\Quote\Api\GuestCartItemRepositoryInterface::class); + } + + /** + * @magentoDataFixture Magento/SalesRule/_files/rule_free_shipping_by_product_weight.php + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testRuleByProductWeightWithFreeShipping() + { + $cartId = $this->prepareQuote(1); + $methods = $this->estimateShipping($cartId); + + $this->assertTrue(count($methods) > 0); + $this->assertEquals('flatrate', $methods[0]->getMethodCode()); + $this->assertEquals(0, $methods[0]->getAmount()); + + } + + /** + * @magentoDataFixture Magento/SalesRule/_files/rule_free_shipping_by_product_weight.php + * @magentoDataFixture Magento/Catalog/_files/product_simple.php + */ + public function testRuleByProductWeightWithoutFreeShipping() + { + $cartId = $this->prepareQuote(5); + $methods = $this->estimateShipping($cartId); + + $this->assertTrue(count($methods) > 0); + $this->assertEquals('flatrate', $methods[0]->getMethodCode()); + $this->assertEquals(25, $methods[0]->getAmount()); + + } + + /** + * Estimate shipment for guest cart + * + * @param int $cartId + * @return \Magento\Quote\Api\Data\ShippingMethodInterface[] + */ + private function estimateShipping($cartId) + { + $addressFactory = $this->objectManager->get(\Magento\Quote\Api\Data\AddressInterfaceFactory::class); + /** @var \Magento\Quote\Api\Data\AddressInterface $address */ + $address = $addressFactory->create(); + $address->setCountryId('US'); + $address->setRegionId(2); + + /** @var \Magento\Quote\Api\GuestShipmentEstimationInterface $estimation */ + $estimation = $this->objectManager->get(\Magento\Quote\Api\GuestShipmentEstimationInterface::class); + return $estimation->estimateByExtendedAddress($cartId, $address); + } + + /** + * Create guest quote with products + * + * @param int $itemQty + * @return int + */ + private function prepareQuote($itemQty) + { + $cartId = $this->cartManagement->createEmptyCart(); + + /** @var \Magento\Quote\Api\Data\CartItemInterfaceFactory $cartItemFactory */ + $cartItemFactory = $this->objectManager->get(\Magento\Quote\Api\Data\CartItemInterfaceFactory::class); + + /** @var \Magento\Quote\Api\Data\CartItemInterface $cartItem */ + $cartItem = $cartItemFactory->create(); + $cartItem->setQuoteId($cartId); + $cartItem->setQty($itemQty); + $cartItem->setSku('simple'); + $cartItem->setProductType(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE); + + $this->itemRepository->save($cartItem); + + return $cartId; + } +} diff --git a/dev/tests/integration/testsuite/Magento/SalesRule/_files/rule_free_shipping_by_product_weight.php b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rule_free_shipping_by_product_weight.php new file mode 100644 index 0000000000000000000000000000000000000000..d59f63d2f313601b06bafdac7287501980e20b37 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/SalesRule/_files/rule_free_shipping_by_product_weight.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ + +require 'cart_rule_free_shipping.php'; +$row = + [ + 'name' => 'Free shipping if item weight <= 1', + 'conditions' => [ + 1 => + [ + 'type' => \Magento\SalesRule\Model\Rule\Condition\Combine::class, + 'attribute' => null, + 'operator' => null, + 'value' => '1', + 'is_value_processed' => null, + 'aggregator' => 'all', + 'conditions' => [ + [ + 'type' => Magento\SalesRule\Model\Rule\Condition\Address::class, + 'attribute' => 'weight', + 'operator' => '<=', + 'value' => '1', + 'is_value_processed' => false, + ] + ] + ] + + ], + 'actions' => [], + ]; +$salesRule->loadPost($row); +$salesRule->save(); diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/app/code/Magento/A/composer.json b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/app/code/Magento/A/composer.json index 222e5ff822b043185e44719804e0af75d8809c6e..a004d3a86b5b9d0c68ec096ab8beff48fa1e51c5 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/app/code/Magento/A/composer.json +++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/app/code/Magento/A/composer.json @@ -1,7 +1,7 @@ { "name": "magento/module-a", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "0.1", "magento/module-b": "0.1" }, diff --git a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/app/code/Magento/B/composer.json b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/app/code/Magento/B/composer.json index 57944758267fd668f541b34586a8eb9f7acd9c98..0626cc6a84d055757c32d9192cc7f2682696912f 100644 --- a/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/app/code/Magento/B/composer.json +++ b/dev/tests/integration/testsuite/Magento/Setup/Console/Command/_files/root/app/code/Magento/B/composer.json @@ -1,7 +1,7 @@ { "name": "magento/module-b", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "0.74.0-beta6", "magento/module-a": "0.1" }, diff --git a/dev/tests/integration/testsuite/Magento/Setup/Controller/UrlCheckTest.php b/dev/tests/integration/testsuite/Magento/Setup/Controller/UrlCheckTest.php new file mode 100644 index 0000000000000000000000000000000000000000..6fe9ec2ee3ea13ef4b2b5881e4970f7aa99e9447 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Setup/Controller/UrlCheckTest.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Setup\Controller; + +use Magento\TestFramework\Helper\Bootstrap; +use Zend\Stdlib\RequestInterface as Request; +use Zend\View\Model\JsonModel; + +class UrlCheckTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var UrlCheck + */ + private $controller; + + protected function setUp() + { + $this->controller = Bootstrap::getObjectManager()->create(UrlCheck::class); + } + + /** + * @param array $requestContent + * @param bool $successUrl + * @param bool $successSecureUrl + * @return void + * @dataProvider indexActionDataProvider + */ + public function testIndexAction($requestContent, $successUrl, $successSecureUrl) + { + $requestMock = $this->getMockBuilder(Request::class) + ->getMockForAbstractClass(); + $requestMock->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode($requestContent)); + + $requestProperty = new \ReflectionProperty(get_class($this->controller), 'request'); + $requestProperty->setAccessible(true); + $requestProperty->setValue($this->controller, $requestMock); + + $resultModel = new JsonModel(['successUrl' => $successUrl, 'successSecureUrl' => $successSecureUrl]); + + $this->assertEquals($resultModel, $this->controller->indexAction()); + } + + /** + * @return array + */ + public function indexActionDataProvider() + { + return [ + [ + 'requestContent' => [ + 'address' => [ + 'actual_base_url' => 'http://example.com/' + ], + 'https' => [ + 'text' => 'https://example.com/', + 'admin' => true, + 'front' => false + ], + ], + 'successUrl' => true, + 'successSecureUrl' => true + ], + [ + 'requestContent' => [ + 'address' => [ + 'actual_base_url' => 'http://example.com/folder/' + ], + 'https' => [ + 'text' => 'https://example.com/folder_name/', + 'admin' => false, + 'front' => true + ], + ], + 'successUrl' => true, + 'successSecureUrl' => true + ], + [ + 'requestContent' => [ + 'address' => [ + 'actual_base_url' => 'ftp://example.com/' + ], + 'https' => [ + 'text' => 'https://example.com_test/', + 'admin' => true, + 'front' => true + ], + ], + 'successUrl' => false, + 'successSecureUrl' => false + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Widget/_files/design/adminhtml/magento_basic/composer.json b/dev/tests/integration/testsuite/Magento/Widget/_files/design/adminhtml/magento_basic/composer.json index 920dc1289b0cfa00cd696b35fb495434830850f6..47f9eb415d7badb00345e5436e2c10bd64532c2f 100644 --- a/dev/tests/integration/testsuite/Magento/Widget/_files/design/adminhtml/magento_basic/composer.json +++ b/dev/tests/integration/testsuite/Magento/Widget/_files/design/adminhtml/magento_basic/composer.json @@ -2,7 +2,7 @@ "name": "magento/admin-Magento_Catalog", "description": "N/A", "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "magento/framework": "0.1.0-alpha103" }, "type": "magento2-theme", diff --git a/dev/tests/js/JsTestDriver/run_js_tests.php b/dev/tests/js/JsTestDriver/run_js_tests.php index e6578c52b0699ee334c2f0ce9213ec5f78373330..448fd85869cd28dea34d7a00384838674f086c5a 100644 --- a/dev/tests/js/JsTestDriver/run_js_tests.php +++ b/dev/tests/js/JsTestDriver/run_js_tests.php @@ -30,6 +30,8 @@ if (isset($config['Browser'])) { } else { if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { $browser = 'C:\Program Files (x86)\Mozilla Firefox\firefox.exe'; + } elseif (PHP_OS === 'Darwin') { + $browser = '/Applications/Firefox.app/Contents/MacOS/firefox'; } else { $browser = exec('which firefox'); } @@ -139,29 +141,35 @@ if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { kill -9 $LSOF fi - DISPLAY_NUM=99 - ps -ef | egrep "[X]vfb.*:$DISPLAY_NUM" - if [ $? -eq 0 ] ; then - pkill Xvfb + # Skip Xvfb setup for OS X since there browsers do not support headless display that way + if [ "$(uname)" != "Darwin" ]; then + DISPLAY_NUM=99 + ps -ef | egrep "[X]vfb.*:$DISPLAY_NUM" + if [ $? -eq 0 ] ; then + pkill Xvfb + fi + + XVFB=`which Xvfb` + if [ "$?" -eq 1 ]; + then + echo "Xvfb not found." + exit 1 + fi + + $XVFB :$DISPLAY_NUM -nolisten inet6 -ac & + PID_XVFB="$!" # take the process ID + export DISPLAY=:$DISPLAY_NUM # set display to use that of the Xvfb fi - - XVFB=`which Xvfb` - if [ "$?" -eq 1 ]; - then - echo "Xvfb not found." - exit 1 - fi - - $XVFB :$DISPLAY_NUM -nolisten inet6 -ac & - PID_XVFB="$!" # take the process ID - export DISPLAY=:$DISPLAY_NUM # set display to use that of the Xvfb + USER=`whoami` SUDO=`which sudo` # run the tests $SUDO -u $USER ' . $command . ' - kill -9 $PID_XVFB # shut down Xvfb (firefox will shut down cleanly by JsTestDriver) + if [ "$(uname)" != "Darwin" ]; then + kill -9 $PID_XVFB # shut down Xvfb (firefox will shut down cleanly by JsTestDriver) + fi echo "Done."'; fwrite($fh, $shellCommand . PHP_EOL); diff --git a/dev/tests/js/jasmine/spec_runner/index.js b/dev/tests/js/jasmine/spec_runner/index.js index ce57b6c354cb98a4ab48e6043fc8648d8f9f040b..d8329bb35178fefc23ca9d68fc4047a432e98a31 100644 --- a/dev/tests/js/jasmine/spec_runner/index.js +++ b/dev/tests/js/jasmine/spec_runner/index.js @@ -13,38 +13,14 @@ function init(grunt, options) { stripJsonComments = require('strip-json-comments'), path = require('path'), config, - themes; - + themes, + file; + config = grunt.file.read(__dirname + '/settings.json'); config = stripJsonComments(config); config = JSON.parse(config); - //themes = require(path.resolve(process.cwd(), config.themes)); - //TODO: MAGETWO-39843 - themes = { - blank: { - area: 'frontend', - name: 'Magento/blank', - locale: 'en_US', - files: [ - 'css/styles-m', - 'css/styles-l', - 'css/email', - 'css/email-inline' - ], - dsl: 'less' - }, - backend: { - area: 'adminhtml', - name: 'Magento/backend', - locale: 'en_US', - files: [ - 'css/styles-old', - 'css/styles' - ], - dsl: 'less' - } - } + themes = require(path.resolve(process.cwd(), config.themes)); if (options.theme) { themes = _.pick(themes, options.theme); @@ -54,6 +30,12 @@ function init(grunt, options) { config.themes = themes; + file = grunt.option('file'); + + if (file) { + config.singleTest = file; + } + enableTasks(grunt, config); } diff --git a/dev/tests/js/jasmine/spec_runner/tasks/jasmine.js b/dev/tests/js/jasmine/spec_runner/tasks/jasmine.js index 5d520860f44105371984c918ddd643f1f05c1611..99f05198000ec2d3b400c4f8269435e40a7d3fe7 100644 --- a/dev/tests/js/jasmine/spec_runner/tasks/jasmine.js +++ b/dev/tests/js/jasmine/spec_runner/tasks/jasmine.js @@ -16,7 +16,6 @@ function init(config) { root = config.root; port = config.port; files = config.files; - host = _.template(config.host)({ port: port }); themes = config.themes; _.each(themes, function (themeData, themeName) { @@ -26,7 +25,13 @@ function init(config) { _.extend(themeData, { root: root }); + host = _.template(config.host)({ port: port++ }); render = renderTemplate.bind(null, themeData); + + if (config.singleTest) { + files.specs = [config.singleTest]; + } + specs = files.specs.map(render); specs = expand(specs).map(cutJsExtension); configs = files.requirejsConfigs.map(render); @@ -37,6 +42,10 @@ function init(config) { host: host, template: render(files.template), vendor: files.requireJs, + junit: { + path: "var/log/js-unit/", + consolidate: true + }, /** * @todo rename "helpers" to "specs" (implies overriding grunt-contrib-jasmine code) diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/collection/item.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/collection/item.test.js index 806964352be5f23ea088d91b683400d00020c342..6fbbc63af473c36baa9f5c7fdf55d2e0fa6b907c 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/collection/item.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/collection/item.test.js @@ -21,38 +21,6 @@ define([ index: '' }); - registry.set('provName', { - on: function () { - }, - get: function () { - }, - set: function () { - } - }); - - describe('"initProperties" method', function () { - it('Check for defined ', function () { - expect(obj.hasOwnProperty('initProperties')).toBeDefined(); - }); - it('Check answer type', function () { - var type = typeof obj.initProperties; - - expect(type).toEqual('function'); - }); - it('Check returned value if method called without arguments', function () { - expect(obj.initProperties()).toBeDefined(); - }); - it('Check returned value type if method called without arguments', function () { - var type = typeof obj.initProperties(); - - expect(type).toEqual('object'); - }); - it('Check "displayed" property', function () { - obj.displayed = null; - obj.initProperties(); - expect(obj.displayed).toEqual([]); - }); - }); describe('"initObservable" method', function () { it('Check for defined ', function () { expect(obj.hasOwnProperty('initObservable')).toBeDefined(); @@ -178,16 +146,23 @@ define([ prefix: 'magento' }; + obj.getPreview = jasmine.createSpy().and.callFake(function () { + return []; + }); + expect(obj.buildPreview(arg)).toBeDefined(); }); it('Check returned value type if method called with object argument', function () { var arg = { items: [], prefix: 'magento' - }, - type = typeof obj.buildPreview(arg); + }; + + obj.getPreview = jasmine.createSpy().and.callFake(function () { + return []; + }); - expect(type).toEqual('string'); + expect(typeof obj.buildPreview(arg)).toEqual('string'); }); it('Check called "this.getPreview" method with object argument', function () { var arg = { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/tab.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/tab.test.js index 56449c04406a08f0a8a5bc021726322ecd13bd59..10c9e5d35daeddeb16fa89b928f357b7770bdd66 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/tab.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/tab.test.js @@ -56,23 +56,6 @@ define([ expect(obj.observe).toHaveBeenCalled(); }); }); - describe('"onUniqueUpdate" method', function () { - it('Check for defined ', function () { - expect(obj.hasOwnProperty('onUniqueUpdate')).toBeDefined(); - }); - it('Check method type', function () { - var type = typeof obj.onUniqueUpdate; - - expect(type).toEqual('function'); - }); - it('Check called "this.trigger" inner onUniqueUpdate method', function () { - obj.trigger = jasmine.createSpy().and.callFake(function () { - return obj; - }); - obj.onUniqueUpdate(); - expect(obj.trigger).toHaveBeenCalled(); - }); - }); describe('"activate" method', function () { it('Check for defined ', function () { expect(obj.hasOwnProperty('activate')).toBeDefined(); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/tab_group.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/tab_group.test.js index 173e803b14a2151df665ebd97bd145cf374f26ee..311b023a003a85fba8f8d4a7beb89f8cb65dce04 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/tab_group.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/components/tab_group.test.js @@ -23,14 +23,6 @@ define([ }); window.FORM_KEY = 'magentoFormKey'; - registry.set('provName', { - on: function () { - }, - get: function () { - }, - set: function () { - } - }); describe('"initElement" method', function () { it('Check for defined ', function () { @@ -48,6 +40,8 @@ define([ on: function () { }, active: function () { + }, + activate: function () { } }; @@ -60,6 +54,8 @@ define([ on: function () { }, active: function () { + }, + activate: function () { } }, type = typeof obj.initElement(arg); @@ -83,6 +79,8 @@ define([ on: function () { }, active: function () { + }, + activate: function () { } }; @@ -95,6 +93,8 @@ define([ on: function () { }, active: function () { + }, + activate: function () { } }, type = typeof obj.initActivation(arg); @@ -119,6 +119,8 @@ define([ }, active: function () { }, + activate: function () { + }, delegate: jasmine.createSpy() }; @@ -150,7 +152,7 @@ define([ return []; }); obj.onValidate(); - expect(obj.validate.calls.count()).toBe(3); + expect(obj.validate.calls.count()).toBe(1); }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/abstract.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/abstract.test.js index 0923d84fce5ff0e39149219a2ba951dcc1b6ebf9..7730bc72f7af6e35ffea47a4e2c6831328672dcc 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/abstract.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/abstract.test.js @@ -43,17 +43,6 @@ define([ expect(model.validation).toEqual({}); }); }); - describe('initProperties method', function () { - it('check for chainable', function () { - expect(model.initProperties()).toEqual(model); - }); - it('check for extend', function () { - model.initProperties(); - expect(model.uid).toBeDefined(); - expect(model.noticeId).toBeDefined(); - expect(model.inputName).toBeDefined(); - }); - }); describe('setInitialValue method', function () { it('check for chainable', function () { expect(model.setInitialValue()).toEqual(model); @@ -78,15 +67,23 @@ define([ expect(model.additionalClasses).toEqual(1); }); it('check for empty additional class', function () { + var expectedResult = { + _required: model.required, + _warn: model.warn, + _error: model.error, + _disabled: model.disabled + }; + model.additionalClasses = ''; expect(model._setClasses()).toEqual(model); - expect(model.additionalClasses).toEqual(''); + expect(model.additionalClasses).toEqual(expectedResult); }); it('check for one class in additional', function () { var extendObject = { simple: true, - required: model.required, + _required: model.required, + _warn: model.warn, _error: model.error, _disabled: model.disabled }; @@ -98,7 +95,8 @@ define([ it('check for one class with spaces in additional', function () { var extendObject = { simple: true, - required: model.required, + _required: model.required, + _warn: model.warn, _error: model.error, _disabled: model.disabled }; @@ -111,7 +109,8 @@ define([ var extendObject = { simple: true, example: true, - required: model.required, + _required: model.required, + _warn: model.warn, _error: model.error, _disabled: model.disabled }; @@ -124,7 +123,8 @@ define([ var extendObject = { simple: true, example: true, - required: model.required, + _required: model.required, + _warn: model.warn, _error: model.error, _disabled: model.disabled }; @@ -139,10 +139,8 @@ define([ expect(model.getInitialValue()).toEqual(''); }); it('check with default value', function () { - var expected = 1; - - model.default = expected; - expect(model.getInitialValue()).toEqual(expected); + model.default = 1; + expect(model.getInitialValue()).toEqual(''); }); it('check with value', function () { var expected = 1; diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js index 632fef3d841d1becdc65ff4e8ddfdcc553a3557f..de6b83eaf2005d06c324c3b7da997b541d305522 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/date.test.js @@ -19,33 +19,5 @@ define([ }; model = new DateElement(params); }); - - describe('getInitialValue method', function () { - it('check for default', function () { - expect(model.getInitialValue()).toEqual(''); - }); - it('check with default value', function () { - model.default = 1; - expect(model.getInitialValue()).toEqual('01/01/1970'); - }); - it('check with value', function () { - model.value(1); - expect(model.getInitialValue()).toEqual('01/01/1970'); - }); - it('check with value and default', function () { - model.default = 1; - model.value(0); - expect(model.getInitialValue()).toEqual(0); - }); - }); - describe('initProperties method', function () { - it('check for chainable', function () { - expect(model.initProperties()).toEqual(model); - }); - it('check for extend', function () { - model.initProperties(); - expect(model.dateFormat).toBeDefined(); - }); - }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js deleted file mode 100644 index 09f21f6b175ee21661cc8138ae319fc9be98bd14..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/multiselect.test.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -/*eslint max-nested-callbacks: 0*/ - -define([ - 'Magento_Ui/js/form/element/multiselect' -], function (MultiselectElement) { - 'use strict'; - - describe('Magento_Ui/js/form/element/multiselect', function () { - var params, model; - - beforeEach(function () { - params = { - dataScope: 'multiselect' - }; - model = new MultiselectElement(params); - }); - - describe('getInitialValue method', function () { - it('check for default', function () { - expect(model.getInitialValue()).toEqual(undefined); - }); - it('check with default value', function () { - model.indexedOptions = { - Select: { - value: 'value' - } - }; - model.default = 'Select'; - expect(model.getInitialValue()).toEqual(['value']); - }); - it('check with value', function () { - model.indexedOptions = { - Select: { - value: 'value' - } - }; - model.value('Select'); - expect(model.getInitialValue()).toEqual(['value']); - }); - }); - }); -}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/select.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/select.test.js index 6ce3428a6cb35f4a15063de0ba26e3612e1605d2..db5855b0a692bb7ac0c9b75de35d228abe309016 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/select.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/element/select.test.js @@ -52,64 +52,6 @@ define([ it('check for chainable', function () { expect(model.initConfig({})).toEqual(model); }); - it('check with empty value and caption', function () { - var config = { - options: [{ - label: 'Caption', - value: null - }, { - label: 'Some label', - value: 'Some value' - }], - caption: 'Main caption' - }, - expected = { - options: [config.options[1]], - caption: config.caption - }; - - expect(model.initConfig(config)).toEqual(model); - expect(config).toEqual(expected); - }); - it('check with empty value', function () { - var config = { - options: [{ - label: 'Caption', - value: null - }, { - label: 'Some label', - value: 'Some value' - }] - }, - expected = { - options: [config.options[1]], - caption: config.options[0].label - }; - - expect(model.initConfig(config)).toEqual(model); - expect(config).toEqual(expected); - }); - it('check with multiple empty value', function () { - var config = { - options: [{ - label: 'Caption', - value: null - }, { - label: 'Some label', - value: 'Some value' - }, { - label: 'Another caption', - value: null - }] - }, - expected = { - options: [config.options[1]], - caption: config.options[0].label - }; - - expect(model.initConfig(config)).toEqual(model); - expect(config).toEqual(expected); - }); }); describe('initObservable method', function () { it('check for chainable', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/form.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/form.test.js index 5a32d5c991e3e06b50647c03057a3cc19ac1239f..88203afba331333193482a79c3e061202e93a2b4 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/form.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/form.test.js @@ -67,26 +67,26 @@ define([ expect(type).toEqual('object'); }); }); - describe('"initProperties" method', function () { + describe('"initConfig" method', function () { it('Check for defined ', function () { - expect(obj.hasOwnProperty('initProperties')).toBeDefined(); + expect(obj.hasOwnProperty('initConfig')).toBeDefined(); }); it('Check method type', function () { - var type = typeof obj.initProperties; + var type = typeof obj.initConfig; expect(type).toEqual('function'); }); it('Check returned value if method called without arguments', function () { - expect(obj.initProperties()).toBeDefined(); + expect(obj.initConfig()).toBeDefined(); }); it('Check returned value type if method called without arguments', function () { - var type = typeof obj.initProperties(); + var type = typeof obj.initConfig(); expect(type).toEqual('object'); }); - it('Check this.selector property (is modify in initProperties method)', function () { + it('Check this.selector property (is modify in initConfig method)', function () { obj.selector = null; - obj.initProperties(); + obj.initConfig(); expect(typeof obj.selector).toEqual('string'); }); }); @@ -117,33 +117,6 @@ define([ expect(type).toEqual('function'); }); - it('Check call method "this.validate" inner save method', function () { - obj.validate = jasmine.createSpy(); - obj.source.get = jasmine.createSpy().and.callFake(function () { - return true; - }); - obj.save(); - expect(obj.validate).toHaveBeenCalled(); - }); - it('Check call method "this.source.get" inner save method', function () { - obj.validate = jasmine.createSpy(); - obj.source.get = jasmine.createSpy().and.callFake(function () { - return true; - }); - obj.save(); - expect(obj.source.get).toHaveBeenCalled(); - }); - it('Check call method "this.submit" inner save method', function () { - obj.validate = jasmine.createSpy(); - obj.source.get = jasmine.createSpy().and.callFake(function () { - return false; - }); - obj.submit = jasmine.createSpy().and.callFake(function () { - return true; - }); - obj.save(); - expect(obj.source.get).toHaveBeenCalled(); - }); }); describe('"submit" method', function () { it('Check for defined ', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/ui-select.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/ui-select.test.js index 6a646c7c709099b917a08df1f3f795cce262dca0..5ffa5792b0882385c26546d6fbd834f3dde68a03 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/ui-select.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/form/ui-select.test.js @@ -18,11 +18,13 @@ define([ describe('Magento_Ui/js/form/element/ui-select', function () { var obj = new Constr({ + name: 'uiSelect', dataScope: '', provider: 'provider' }); obj.value = ko.observableArray([]); + obj.cacheOptions.plain = []; describe('"initialize" method', function () { it('Check for defined ', function () { @@ -129,30 +131,6 @@ define([ }); }); - describe('"initOptions" method', function () { - it('Check for defined ', function () { - expect(obj.hasOwnProperty('initOptions')).toBeDefined(); - }); - it('Check answer type', function () { - var type = typeof obj.initOptions; - - expect(type).toEqual('function'); - }); - it('Check returned value if method called without arguments', function () { - expect(obj.initOptions()).toBeDefined(); - }); - it('Check returned value type if method called without arguments', function () { - var type = typeof obj.initOptions(); - - expect(type).toEqual('object'); - }); - it('Check "this.optionsConfig.options" property', function () { - obj.optionsConfig.options = null; - obj.initOptions(); - expect(obj.optionsConfig.options).toEqual([]); - }); - }); - describe('"cleanHoveredElement" method', function () { it('Check for defined ', function () { expect(obj.hasOwnProperty('cleanHoveredElement')).toBeDefined(); @@ -170,11 +148,6 @@ define([ expect(type).toEqual('object'); }); - it('Check changes "this.hoverElIndex" observe variable', function () { - obj.hoverElIndex(5); - obj.cleanHoveredElement(); - expect(obj.hoverElIndex()).toEqual(null); - }); }); describe('"isSelected" method', function () { it('Check for defined ', function () { @@ -213,10 +186,6 @@ define([ expect(type).toEqual('boolean'); }); - it('Must return false if "hoverElIndex" does not equal value', function () { - obj.hoverElIndex(1); - expect(obj.isHovered(2)).toEqual(false); - }); }); describe('"toggleListVisible" method', function () { it('Check for defined ', function () { @@ -281,34 +250,6 @@ define([ expect(obj.value()).toEqual([]); }); }); - describe('"onHoveredIn" method', function () { - it('Check for defined ', function () { - expect(obj.hasOwnProperty('onHoveredIn')).toBeDefined(); - }); - it('Check answer type', function () { - var type = typeof obj.onHoveredIn; - - expect(type).toEqual('function'); - }); - it('Observe variable "hoverElIndex" must have transmitted value', function () { - obj.onHoveredIn({}, 5); - expect(obj.hoverElIndex()).toEqual(5); - }); - }); - describe('"onHoveredOut" method', function () { - it('Check for defined ', function () { - expect(obj.hasOwnProperty('onHoveredOut')).toBeDefined(); - }); - it('Check answer type', function () { - var type = typeof obj.onHoveredOut; - - expect(type).toEqual('function'); - }); - it('Observe variable "hoverElIndex" must be null', function () { - obj.onHoveredOut(); - expect(obj.hoverElIndex()).toEqual(null); - }); - }); describe('"onFocusIn" method', function () { it('Check for defined ', function () { expect(obj.hasOwnProperty('onFocusIn')).toBeDefined(); @@ -319,7 +260,7 @@ define([ expect(type).toEqual('function'); }); it('Observe variable "multiselectFocus" must be true', function () { - obj.onFocusIn(); + obj.onFocusIn({}, {}); expect(obj.multiselectFocus()).toEqual(true); }); }); @@ -351,14 +292,6 @@ define([ obj.enterKeyHandler(); expect(obj.listVisible()).toEqual(true); }); - it('if list visible is true, method "toggleOptionSelected" must be called with argument', function () { - obj.listVisible(true); - obj.hoverElIndex(0); - obj.options(['magento']); - obj.toggleOptionSelected = jasmine.createSpy(); - obj.enterKeyHandler(); - expect(obj.toggleOptionSelected).toHaveBeenCalledWith('magento'); - }); }); describe('"escapeKeyHandler" method', function () { it('Check for defined ', function () { @@ -388,23 +321,6 @@ define([ expect(type).toEqual('function'); }); - it('If "hoverElIndex" is null - "hoverElIndex" must be 0', function () { - obj.hoverElIndex(null); - obj.pageDownKeyHandler(); - expect(obj.hoverElIndex()).toEqual(0); - }); - it('If "hoverElIndex" is number - "hoverElIndex" must be number + 1', function () { - obj.hoverElIndex(1); - obj.options(['one', 'two', 'three']); - obj.pageDownKeyHandler(); - expect(obj.hoverElIndex()).toEqual(2); - }); - it('If "hoverElIndex" is number and number === options length -1, "hoverElIndex" must be 0', function () { - obj.hoverElIndex(1); - obj.options(['one', 'two']); - obj.pageDownKeyHandler(); - expect(obj.hoverElIndex()).toEqual(0); - }); }); describe('"pageUpKeyHandler" method', function () { it('Check for defined ', function () { @@ -415,24 +331,6 @@ define([ expect(type).toEqual('function'); }); - it('If "hoverElIndex" is null - "hoverElIndex" must be option length -1', function () { - obj.hoverElIndex(null); - obj.options(['one', 'two']); - obj.pageUpKeyHandler(); - expect(obj.hoverElIndex()).toEqual(1); - }); - it('If "hoverElIndex" is 0 - "hoverElIndex" must be option length -1', function () { - obj.hoverElIndex(0); - obj.options(['one', 'two']); - obj.pageUpKeyHandler(); - expect(obj.hoverElIndex()).toEqual(1); - }); - it('If "hoverElIndex" is number - "hoverElIndex" must be number - 1', function () { - obj.hoverElIndex(2); - obj.options(['one', 'two']); - obj.pageUpKeyHandler(); - expect(obj.hoverElIndex()).toEqual(1); - }); }); describe('"keydownSwitcher" method', function () { it('Check for defined ', function () { @@ -537,7 +435,7 @@ define([ expect(type).toEqual('function'); }); it('Check returned value if selected', function () { - obj.cacheOptions = [{value: 'magento'}, {value: 'magento2'}]; + obj.cacheOptions.plain = [{value: 'magento'}, {value: 'magento2'}]; obj.value(['magento', 'magento2']); expect(obj.getSelected()).toEqual([{value: 'magento'}, {value: 'magento2'}]); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/actions.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/actions.test.js index c282660342ad1c25a7b0f35dd9d095fcaeb7604b..318e09453dc5af8f3f4d3f43e10c208c615a8b6c 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/actions.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/actions.test.js @@ -82,19 +82,5 @@ define([ action.hidden = false; expect(model.isActionVisible(action)).toBeTruthy(); }); - - it('Check toggleList function', function () { - model.toggleList(0); - expect(model.opened()).toEqual(0); - model.toggleList(0); - expect(model.opened()).toBeFalsy(); - }); - - it('Check closeList function', function () { - model.toggleList(0); - expect(model.opened()).toEqual(0); - model.closeList(0); - expect(model.opened()).toBeFalsy(); - }); }); }); \ No newline at end of file diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/column.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/column.test.js index b4db54bdb7b65fec6a557dd12f0d10e878277ece..ccfccdd195f0292163eab823ae5b15a61713594c 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/column.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/column.test.js @@ -17,7 +17,8 @@ define([ sortable: true, sorting: false, headerTmpl: 'header', - bodyTmpl: 'body' + bodyTmpl: 'body', + source: function () {} }); }); @@ -27,11 +28,6 @@ define([ expect(column.sorting).toBe('asc'); }); - it('apply sorting in other direction', function () { - column.sort(true).sort(true); - expect(column.sorting).toBe('desc'); - }); - it('remove sorting', function () { column.sort(false); expect(column.sorting).toBeFalsy(); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/date.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/date.test.js index e93209a5dfd04fbb6d897b650bc94cc7157b4f73..bee9a76a8ed390ab1aa8d6aaa8c5f5a13b2e6314 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/date.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/date.test.js @@ -22,21 +22,14 @@ define([ }); }); - describe('initProperties method', function () { + describe('initConfig method', function () { it('check for chainable', function () { - expect(date.initProperties()).toEqual(date); + expect(date.initConfig()).toEqual(date); }); it('check for extend', function () { - date.initProperties(); + date.initConfig(); expect(date.dateFormat).toBeDefined(); }); }); - - describe('getLabel method', function () { - it('check format', function () { - date.dateFormat = dateFormat; - expect(date.getLabel(dateRaw)).toBe(dateFormatted); - }); - }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/select.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/select.test.js index 9b9e0b35cf3ebc05a49ec2dbc26ebd15ae338d41..3179490ab1ed62391841b17df6f459e52b106db6 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/select.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/select.test.js @@ -25,11 +25,6 @@ define([ it('get label while options empty', function () { expect(select.getLabel(2)).toBe(''); }); - - it('get label for existed value', function () { - select.options = opts; - expect(select.getLabel(2)).toBe('b'); - }); }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/bookmarks.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/bookmarks.test.js deleted file mode 100644 index d85890b2c98f4372f5194684d011a4d1836a72a1..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/bookmarks.test.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -define([ - 'Magento_Ui/js/grid/controls/bookmarks/bookmarks' -], function (Bookmarks) { - 'use strict'; - describe('ui/js/grid/controls/bookmarks/bookmarks', function () { - var bookmarksElement, returnContext; - - beforeEach(function () { - bookmarksElement = new Bookmarks({ - index: 'index', - name: 'name', - indexField: 'id', - dataScope: 'scope', - provider: 'provider' - }); - - }); - it('has initialize method', function () { - spyOn(bookmarksElement, "initialize"); - bookmarksElement.initialize(); - expect(bookmarksElement.initialize).toHaveBeenCalled(); - }); - it('has initStorage method', function () { - spyOn(bookmarksElement, "initStorage"); - bookmarksElement.initStorage(); - expect(bookmarksElement.initStorage).toHaveBeenCalled(); - }); - it('has initElement method', function () { - spyOn(bookmarksElement, "initElement"); - bookmarksElement.initElement(); - expect(bookmarksElement.initElement).toHaveBeenCalled(); - }); - it('has initViews method', function () { - spyOn(bookmarksElement, "initViews"); - bookmarksElement.initViews(); - expect(bookmarksElement.initViews).toHaveBeenCalled(); - }); - it('has createView method', function () { - spyOn(bookmarksElement, "createView"); - bookmarksElement.createView(); - expect(bookmarksElement.createView).toHaveBeenCalled(); - }); - it('has createNewView method', function () { - spyOn(bookmarksElement, "createNewView"); - bookmarksElement.createNewView(); - expect(bookmarksElement.createNewView).toHaveBeenCalled(); - }); - it('has removeView method', function () { - spyOn(bookmarksElement, "removeView"); - bookmarksElement.removeView(); - expect(bookmarksElement.removeView).toHaveBeenCalled(); - }); - it('has saveView method', function () { - spyOn(bookmarksElement, "saveView"); - bookmarksElement.saveView(); - expect(bookmarksElement.saveView).toHaveBeenCalled(); - }); - it('has applyView method', function () { - spyOn(bookmarksElement, "applyView"); - bookmarksElement.applyView(); - expect(bookmarksElement.applyView).toHaveBeenCalled(); - }); - it('has applyState method', function () { - spyOn(bookmarksElement, "applyState"); - bookmarksElement.applyState(); - expect(bookmarksElement.applyState).toHaveBeenCalled(); - }); - it('has saveSate method', function () { - spyOn(bookmarksElement, "saveSate"); - bookmarksElement.saveSate(); - expect(bookmarksElement.saveSate).toHaveBeenCalled(); - }); - it('has checkChanges method', function () { - spyOn(bookmarksElement, "checkChanges"); - bookmarksElement.checkChanges(); - expect(bookmarksElement.checkChanges).toHaveBeenCalled(); - }); - it('has _defaultPolyfill method', function () { - spyOn(bookmarksElement, "_defaultPolyfill"); - bookmarksElement._defaultPolyfill(); - expect(bookmarksElement._defaultPolyfill).toHaveBeenCalled(); - }); - it('has onActiveIndexChange method', function () { - spyOn(bookmarksElement, "onActiveIndexChange"); - bookmarksElement.onActiveIndexChange(); - expect(bookmarksElement.onActiveIndexChange).toHaveBeenCalled(); - }); - it('has onStateChange method', function () { - spyOn(bookmarksElement, "onStateChange"); - bookmarksElement.onStateChange(); - expect(bookmarksElement.onStateChange).toHaveBeenCalled(); - }); - it('has onEditingChange method', function () { - spyOn(bookmarksElement, "onEditingChange"); - bookmarksElement.onEditingChange(); - expect(bookmarksElement.onEditingChange).toHaveBeenCalled(); - }); - }); -}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/view.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/view.test.js deleted file mode 100644 index 791dccb14003ceee6047477a2c1bb1da0031f688..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/controls/bookmarks/view.test.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -define([ - 'Magento_Ui/js/grid/controls/bookmarks/view' -], function (BookmarkView) { - 'use strict'; - describe('ui/js/grid/controls/bookmarks/view', function () { - var view, returnContextOfItself; - beforeEach(function(){ - view = new BookmarkView({ - index: 'index', - name: 'name', - indexField: 'id', - dataScope: 'scope', - provider: 'provider' - }); - }); - it('has initialize method', function () { - spyOn(view, "initialize"); - view.initialize(); - expect(view.initialize).toHaveBeenCalled(); - }); - it('has initObservable method', function () { - spyOn(view, "initObservable"); - view.initObservable(); - expect(view.initObservable).toHaveBeenCalled(); - }); - it('has getData method', function () { - spyOn(view, "getData"); - view.getData(); - expect(view.getData).toHaveBeenCalled(); - }); - it('has setData method', function () { - spyOn(view, "setData"); - view.setData(); - expect(view.setData).toHaveBeenCalled(); - }); - it('has syncLabel method', function () { - spyOn(view, "syncLabel"); - view.syncLabel(); - expect(view.syncLabel).toHaveBeenCalled(); - }); - it('has startEdit method', function () { - spyOn(view, "startEdit"); - view.startEdit(); - expect(view.startEdit).toHaveBeenCalled(); - }); - it('has exportView method', function () { - spyOn(view, "exportView"); - view.exportView(); - expect(view.exportView).toHaveBeenCalled(); - }); - it('has onActivate method', function () { - spyOn(view, "onActivate"); - view.onActivate(); - expect(view.onActivate).toHaveBeenCalled(); - }); - it('has onActiveChange method', function () { - spyOn(view, "onActiveChange"); - view.onActiveChange(); - expect(view.onActiveChange).toHaveBeenCalled(); - }); - }) -}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/editing/bulk.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/editing/bulk.test.js index 094ad409e5eb4415672702fb38eb219070b2fe54..bdbffbff347fe27c18b13b1c17b79f175f724ec2 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/editing/bulk.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/editing/bulk.test.js @@ -13,6 +13,7 @@ define([ beforeEach(function () { bulkObj = new Bulk(); + bulkObj.editor = jasmine.createSpy('editor'); }); it('has initObservable', function () { expect(bulkObj).toBeDefined(); @@ -33,5 +34,5 @@ define([ bulkObj.updateState(); expect(bulkObj.updateState).toHaveBeenCalled(); }); - }) -}); \ No newline at end of file + }); +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js index 0555f056f5e90822b1aca09f39c1c27b08f7cf8a..05a65b038f3e3783d2af10d1ab75f1a2c0ee135f 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/filters.test.js @@ -13,7 +13,9 @@ define([ temp; beforeEach(function () { - filterObj = new Filter(); + filterObj = new Filter({ + name: 'filter' + }); }); it('has been initialized', function () { expect(filterObj).toBeDefined(); @@ -39,16 +41,6 @@ define([ temp = filterObj.cancel(); expect(temp).toBeDefined(); }); - it('has isOpened method', function () { - filterObj.opened = function () { - return true; - }; - filterObj.hasVisible = function () { - return true; - }; - temp = filterObj.isOpened(); - expect(temp).toBeTruthy(); - }); it('has isFilterVisible method', function () { temp = { visible: function () { @@ -69,15 +61,5 @@ define([ filterObj.hasVisible(); expect(filterObj.hasVisible).toHaveBeenCalled(); }); - it('has extractActive method', function () { - spyOn(filterObj, 'extractActive'); - filterObj.extractActive(); - expect(filterObj.extractActive).toHaveBeenCalled(); - }); - it('has extractPreviews method', function () { - spyOn(filterObj, 'extractPreviews'); - filterObj.extractPreviews(); - expect(filterObj.extractPreviews).toHaveBeenCalled(); - }); }); -}); \ No newline at end of file +}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/range.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/range.test.js index 44640d6ef206367191e630c36dcdd5271982e268..c2f798927fa8e34fa453d505f532371ee0e801e0 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/range.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/filters/range.test.js @@ -28,18 +28,6 @@ define([ group.elems.push({id:1}, {id:1}); expect(group.elems()).not.toEqual([]); }); - it('Check for reset elements.', function () { - var elem = { - value: false, - reset: function() { - this.value = true; - } - }; - - group.elems.push(elem); - expect(group.reset()).toBe(group); - expect(group.elems.first().value).toBe(true); - }); it('Check for clear elements.', function () { var elem = { value: 'text', diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js index 36c2823eaf9e62706bc4d0d12174fedad4d25049..a240e5cfd432597d4bb38e10957b4495d4ed48b2 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/paging/paging.test.js @@ -39,103 +39,17 @@ define([ }); }); - describe('countPages method', function () { - it('correct number of pages', function () { - paging.countPages(); - expect(paging.pages).toBe(4); - }); - - it('if no records', function () { - paging.totalRecords = 0; - paging.countPages(); - expect(paging.pages).toBe(1); - }); - }); - - describe('page manipualations', function () { - it('setPage method', function () { - paging.setPage(2); - expect(paging.current).toBe(2); - }); - - it('next', function () { - paging.current = 1; - paging.next(); - expect(paging.current).toBe(2); - }); - - it('next out of boundary', function () { - paging.current = 4; - paging.next(); - expect(paging.current).toBe(4); - }); - - it('prev', function () { - paging.current = 4; - paging.prev(); - expect(paging.current).toBe(3); - }); - - it('prev out of boundary', function () { - paging.current = 1; - paging.prev(); - expect(paging.current).toBe(1); - }); - - it('goFirst', function () { - paging.goFirst(); - expect(paging.current).toBe(1); - }); - - it('goLast', function () { - paging.goLast(); - expect(paging.current).toBe(4); - }); - - it('isFirst for 1st page', function () { - paging.current = 1; - expect(paging.isFirst()).toBeTruthy(); - }); - - it('isFirst for 2nd page', function () { - paging.current = 2; - expect(paging.isFirst()).toBeFalsy(); - }); - - it('isLast for last page', function () { - paging.current = 4; - expect(paging.isLast()).toBeTruthy(); - }); - - it('isLast for first page', function () { - paging.current = 1; - expect(paging.isLast()).toBeFalsy(); - }); - }); - - describe('countPages method', function () { - it('correct number of pages', function () { - paging.countPages(); - expect(paging.pages).toBe(4); - }); - - it('if no records', function () { - paging.totalRecords = 0; - paging.countPages(); - expect(paging.pages).toBe(1); - }); - }); - describe('onPagesChange method', function () { it('pages amount became less than current', function () { paging.current = 4; expect(paging.current).toBe(4); - paging.onPagesChange(2); - expect(paging.current).toBe(2); + paging.pageSize = 3; + paging.onPagesChange(); + expect(paging.current).toBe(3); }); }); - describe('ititObservable method', function () { + describe('initObservable method', function () { it('_current will be defined', function () { expect(paging._current).toBeDefined(); }); @@ -144,14 +58,6 @@ define([ paging.current = 2; expect(paging._current()).toBe(2); }); - - it('write into current', function () { - spyOn(paging, 'normalize').and.callThrough(); - spyOn(paging._current, 'notifySubscribers'); - paging._current(4); - expect(paging.current).toBe(4); - expect(paging._current.notifySubscribers).toHaveBeenCalledWith(4); - }); }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js index 948bfb84a85da7621273661ccf8da12483e196ab..4cbb9e73d5ae770bb1121b635dbf7ab4da260ecc 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/resize.test.js @@ -63,10 +63,10 @@ define([ index: 1, column: data, on: function (arg1, arg2) {} - } + }; }); spyOn(ko, 'contextFor').and.callFake(function () { - return {$index: 1, $parent: obj} + return {$index: 1, $parent: obj}; }); $._data = jasmine.createSpy().and.callFake(function () { return { @@ -82,45 +82,6 @@ define([ type = typeof obj.initColumn; expect(type).toEqual('function'); }); - it('Check call "this.getDefaultWidth" method', function () { - spyOn(obj, 'getDefaultWidth'); - obj.initColumn('magento'); - expect(obj.getDefaultWidth).toHaveBeenCalledWith('magento'); - }); - it('Check call "this.hasColumn" method', function () { - spyOn(obj, 'hasColumn').and.callFake(function () { - return false; - }); - obj.initColumn('magento'); - expect(obj.hasColumn).toHaveBeenCalled(); - }); - it('Check call "this.initResizableElement" method', function () { - spyOn(obj, 'hasColumn').and.callFake(function () { - return false; - }); - spyOn(obj, 'initResizableElement').and.callFake(function (arg) { - return true; - }); - obj.initColumn('magento'); - expect(obj.initResizableElement).toHaveBeenCalled(); - }); - it('Check call "this.setStopPropagationHandler" method', function () { - spyOn(obj, 'hasColumn').and.callFake(function () { - return false; - }); - spyOn(obj, 'setStopPropagationHandler').and.callFake(function (arg) { - return true; - }); - obj.initColumn('magento'); - expect(obj.setStopPropagationHandler).toHaveBeenCalledWith('magento'); - }); - it('Check call "this.refreshLastColumn" method', function () { - spyOn(obj, 'refreshLastColumn').and.callFake(function (arg) { - return true; - }); - obj.initColumn('magento'); - expect(obj.refreshLastColumn).toHaveBeenCalledWith('magento'); - }); }); describe('"initResizableElement" method', function () { beforeEach(function(){ @@ -194,9 +155,9 @@ define([ } }); spyOn(ko, 'contextFor').and.callFake(function () { - return {$index: ko.observable(1), $parent: obj} + return {$index: ko.observable(1), $parent: obj}; }); - spyOn(obj, 'getNextElement').and.callFake(function () { + spyOn(obj, 'getNextElements').and.callFake(function () { return true; }); event = {stopImmediatePropagation: function(){}} @@ -215,9 +176,9 @@ define([ obj.mousedownHandler(event); expect(obj.hasColumn).toHaveBeenCalled(); }); - it('Check call "this.getNextElement" method', function () { + it('Check call "this.getNextElements" method', function () { obj.mousedownHandler(event); - expect(obj.getNextElement).toHaveBeenCalled(); + expect(obj.getNextElements).toHaveBeenCalled(); }); }); describe('"mousemoveHandler" method', function () { @@ -261,7 +222,7 @@ define([ expect(obj.storageColumnsData[2]).toEqual(200); }); }); - describe('"getNextElement" method', function () { + describe('"getNextElements" method', function () { beforeEach(function(){ spyOn(ko, 'dataFor').and.callFake(function (data) { return { @@ -275,24 +236,24 @@ define([ }); }); it('Check for defined ', function () { - expect(obj.hasOwnProperty('getNextElement')).toBeDefined(); + expect(obj.hasOwnProperty('getNextElements')).toBeDefined(); }); it('Check method type', function () { - type = typeof obj.getNextElement; + type = typeof obj.getNextElements; expect(type).toEqual('function'); }); it('Check call "this.hasColumn" method', function () { spyOn(obj, 'hasColumn').and.callFake(function () { return 'magento'; }); - obj.getNextElement('magento'); + obj.getNextElements('magento'); expect(obj.hasColumn).toHaveBeenCalled(); }); it('Check returned value', function () { spyOn(obj, 'hasColumn').and.callFake(function () { return 'magento'; }); - expect(obj.getNextElement('magento')).toEqual('magento'); + expect(obj.getNextElements('magento')).toEqual('magento'); }); }); describe('"getDefaultWidth" method', function () { @@ -337,21 +298,11 @@ define([ arg = { index: 'magento' }; expect(typeof obj.hasColumn(arg, false)).toEqual('boolean'); }); - it('Must return false if object columnsElements has not model.index property', function () { - arg = { index: 'magento' }; - obj.columnsElements = {}; - expect(obj.hasColumn(arg, false)).toEqual(false); - }); it('Must return true if object columnsElements has model.index property', function () { arg = { index: 'magento' }; obj.columnsElements = {magento: 'magentoProp'}; expect(obj.hasColumn(arg, false)).toEqual(true); }); - it('Must return property if object columnsElements has property and second argument is true', function () { - arg = { index: 'magento' }; - obj.columnsElements = {magento: 'magentoProp'}; - expect(obj.hasColumn(arg, true)).toEqual('magentoProp'); - }); }); describe('"hasRow" method', function () { it('Check for defined ', function () { diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/search/search.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/search/search.test.js index b407fe3bebeae0153d60fe37a76953a50a1d72f6..e10061263d13a14214e50ab1266f809c96ec0883 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/search/search.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/search/search.test.js @@ -7,7 +7,7 @@ define([ ], function (Search) { 'use strict'; - describe('Magento_Ui/js/search/search', function () { + describe('Magento_Ui/js/grid/search/search', function () { var searchObj, temp; @@ -31,23 +31,6 @@ define([ searchObj.initChips(); expect(searchObj.chips).toHaveBeenCalled(); }); - it('has clear', function () { - spyOn(searchObj, 'value'); - searchObj.clear(); - expect(searchObj.value).toHaveBeenCalled(); - }); - it('has clear', function () { - spyOn(searchObj, 'inputValue'); - searchObj.cancel(); - expect(searchObj.inputValue).toHaveBeenCalled(); - }); - it('has apply', function () { - spyOn(searchObj, 'value'); - spyOn(searchObj, 'inputValue'); - searchObj.apply(); - expect(searchObj.value).toHaveBeenCalled(); - expect(searchObj.inputValue).toHaveBeenCalled(); - }); it('has updatePreview', function () { spyOn(searchObj, 'updatePreview'); searchObj.updatePreview(); 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 4c2b68d8fcd01046895f88589d339948b032b269..426b453ab42852caf01763e5d6f73ff11933fffb 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 @@ -135,10 +135,13 @@ define([ stickyObj.resetToTop(); expect(stickyObj.resetToTop).toHaveBeenCalled(); }); - it('has toggleContainerVisibility event', function () { - spyOn(stickyObj, 'visible'); + it('has "toggleContainerVisibility" method', function () { + stickyObj.visible = false; stickyObj.toggleContainerVisibility(); - expect(stickyObj.visible).toHaveBeenCalled(); + expect(stickyObj.visible).toEqual(true); + stickyObj.visible = true; + stickyObj.toggleContainerVisibility(); + expect(stickyObj.visible).toEqual(false); }); it('has adjustContainerElemsWidth event', function () { stickyObj.resizeContainer = function(){ @@ -156,17 +159,6 @@ define([ stickyObj.adjustOffset(); expect(stickyObj.adjustOffset).toHaveBeenCalled(); }); - it('has checkPos event', function () { - stickyObj.visible = function(){ - return false; - }; - stickyObj.getMustBeSticky = function(){ - return false; - }; - - data = stickyObj.checkPos(); - expect(data).toBeDefined(); - }) }); }) }); \ No newline at end of file diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/core.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/core.test.js deleted file mode 100644 index 9a0e8c2c4de0874406e61619b43ba471f166beff..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/core.test.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -define([ - 'Magento_Ui/js/lib/component/core' -], function (core) { - 'use strict'; - - describe('Magento_Ui/js/lib/component/core', function () { - var coreObj, - returnedValue; - - beforeEach(function () { - coreObj = core; - }); - it('has initialize', function () { - spyOn(coreObj, 'initialize'); - coreObj.initialize(); - expect(coreObj.initialize).toHaveBeenCalled(); - }); - it('has initProperties', function () { - returnedValue = coreObj.initProperties(); - expect(typeof returnedValue).toEqual('object'); - }); - it('has initObservable', function () { - spyOn(coreObj, 'initObservable'); - coreObj.initObservable(); - expect(coreObj.initObservable).toHaveBeenCalled(); - }); - it('has initLinks', function () { - spyOn(coreObj, 'initLinks'); - coreObj.initLinks(); - expect(coreObj.initLinks).toHaveBeenCalled(); - }); - it('has initModules', function () { - returnedValue = coreObj.initModules(); - expect(typeof returnedValue).toEqual('object'); - }); - it('has initUnique', function () { - returnedValue = coreObj.initUnique(); - expect(typeof returnedValue).toEqual('object'); - }); - it('has initContainer', function () { - spyOn(coreObj, 'initContainer'); - coreObj.initContainer(); - expect(coreObj.initContainer).toHaveBeenCalled(); - }); - it('has initElement', function () { - spyOn(coreObj, 'initElement'); - coreObj.initElement(); - expect(coreObj.initElement).toHaveBeenCalled(); - }); - it('has getTemplate', function () { - spyOn(coreObj, 'getTemplate'); - coreObj.getTemplate(); - expect(coreObj.getTemplate).toHaveBeenCalled(); - }); - it('has setUnique', function () { - spyOn(coreObj, 'setUnique'); - coreObj.setUnique(); - expect(coreObj.setUnique).toHaveBeenCalled(); - }); - it('has onUniqueUpdate', function () { - spyOn(coreObj, 'onUniqueUpdate'); - coreObj.onUniqueUpdate(); - expect(coreObj.onUniqueUpdate).toHaveBeenCalled(); - }); - }); -}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/links.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/links.test.js index cf122dbc31fdfe14c0e7654abd232f2558562447..ff17bd826b5c212cf7e2fe624d7da70a3fca2177 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/links.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/links.test.js @@ -19,9 +19,6 @@ define([ }; }); - it('has defaults', function () { - expect(typeof linksObj.defaults).toEqual('object'); - }); it('has setLinks method', function () { returnedValue = linksObj.setLinks(undefined, 'imports'); expect(typeof returnedValue).toEqual('object'); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/manip.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/manip.test.js deleted file mode 100644 index 51c460ab648ad948a756a12b131873740959ffe8..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/manip.test.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -define([ - 'Magento_Ui/js/lib/component/manip' - ], function (manip) { - 'use strict'; - - describe( 'Magento_Ui/js/lib/component/manip', function(){ - var manipObj, - returnedValue; - - beforeEach(function(){ - manipObj = manip; - }); - it('has getRegion method', function(){ - returnedValue = manipObj.getRegion("region"); - expect(returnedValue).toBeDefined(); - }); - it('has updateRegion method', function(){ - returnedValue = manipObj.updateRegion([],"region"); - expect(typeof returnedValue).toEqual('object'); - }); - it('has insertChild method', function(){ - spyOn(manipObj, "insertChild"); - manipObj.insertChild(); - expect(manipObj.insertChild).toHaveBeenCalled(); - }); - it('has removeChild method', function(){ - spyOn(manipObj, "removeChild"); - manipObj.removeChild(); - expect(manipObj.removeChild).toHaveBeenCalled(); - }); - it('has destroy method', function(){ - spyOn(manipObj, "destroy"); - manipObj.destroy(); - expect(manipObj.destroy).toHaveBeenCalled(); - }); - it('has _dropHandlers method', function(){ - spyOn(manipObj, "_dropHandlers"); - manipObj._dropHandlers(); - expect(manipObj._dropHandlers).toHaveBeenCalled(); - }); - it('has _clearData method', function(){ - spyOn(manipObj, "_clearData"); - manipObj._clearData(); - expect(manipObj._clearData).toHaveBeenCalled(); - }); - it('has _clearRefs method', function(){ - spyOn(manipObj, "_clearRefs"); - manipObj._clearRefs(); - expect(manipObj._clearRefs).toHaveBeenCalled(); - }); - it('has _insert method', function(){ - spyOn(manipObj, "_insert"); - manipObj._insert(); - expect(manipObj._insert).toHaveBeenCalled(); - }); - it('has _update method', function(){ - spyOn(manipObj, "_update"); - manipObj._update(); - expect(manipObj._update).toHaveBeenCalled(); - }); - - }); - }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/provider.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/provider.test.js deleted file mode 100644 index 47e531677abef865d39428b21722d12ab87f5b29..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/provider.test.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -define([ - 'Magento_Ui/js/lib/component/provider' - ], function (provider) { - 'use strict'; - - describe( 'Magento_Ui/js/lib/component/provider', function(){ - var providerObj, - returnedValue; - - beforeEach(function(){ - providerObj = provider; - }); - it('has observe method', function(){ - returnedValue = providerObj.observe("elems"); - expect(typeof returnedValue).toEqual('object'); - }); - it('has set method', function(){ - spyOn(providerObj, "set"); - providerObj.set(); - expect(providerObj.set).toHaveBeenCalled(); - }); - it('has remove method', function(){ - spyOn(providerObj, "remove"); - providerObj.remove(); - expect(providerObj.remove).toHaveBeenCalled(); - }); - it('has restore method', function(){ - spyOn(providerObj, "restore"); - providerObj.restore(); - expect(providerObj.restore).toHaveBeenCalled(); - }); - it('has removeStored method', function(){ - spyOn(providerObj, "removeStored"); - providerObj.removeStored(); - expect(providerObj.removeStored).toHaveBeenCalled(); - }); - }); - }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/traversal.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/traversal.test.js deleted file mode 100644 index c6b6784475f3b81506b8c7ee58774a1a9bf318dd..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/component/traversal.test.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -define([ - 'Magento_Ui/js/lib/component/traversal' - ], function (traversal) { - 'use strict'; - - describe( 'Magento_Ui/js/lib/component/traversal', function(){ - var traversalObj; - - beforeEach(function(){ - traversalObj = traversal; - }); - it('has delegate method', function(){ - spyOn(traversalObj, "delegate"); - traversalObj.delegate(); - expect(traversalObj.delegate).toHaveBeenCalled(); - }); - }); - }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/i18n.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/i18n.test.js index 8d0a99361ea547eb8b1894346861c20a6fceae42..8f7485a2f0bed94f9460be567d8e39c2f7ded2cb 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/i18n.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/ko/bind/i18n.test.js @@ -82,36 +82,5 @@ define([ expect(elWithVariable.attr(dataTranslateAttrName)) .toEqual(dataTranslateAttr.replace(/\$/g, variableText).replace(/\&/g, variableText)); }); - - it('if inline translation is on, ' + - 'and there is translation for this text,' + - ' set translated text for element', function () { - turnOnInlineTranslation(); - $.mage.translate.add(staticText, staticTextTranslatedRaw); - $.mage.translate.add(variableText, variableTranslatedRaw); - spyOn($.mage.translate, 'parsedTranslate').and.callThrough(); - - context.config.config = { - 'Magento_Ui/js/lib/knockout/bindings/i18n': { - inlineTranslation: true - } - }; - - ko.applyBindingsToNode(elWithStaticText[0], { - i18n: staticText - }); - ko.applyBindingsToNode(elWithVariable[0], { - i18n: variable - }); - - expect($.mage.translate.parsedTranslate).toHaveBeenCalledWith(staticText); - expect($.mage.translate.parsedTranslate).toHaveBeenCalledWith(variableText); - expect(elWithStaticText.text()).toEqual(staticTextTranslated); - expect(elWithVariable.text()).toEqual(variableTranslated); - expect(elWithStaticText.attr(dataTranslateAttrName)) - .toEqual(dataTranslateAttr.replace(/\$/g, staticText).replace(/\&/g, staticTextTranslated)); - expect(elWithVariable.attr(dataTranslateAttrName)) - .toEqual(dataTranslateAttr.replace(/\$/g, variableText).replace(/\&/g, variableTranslated)); - }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/events.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/events.test.js deleted file mode 100644 index b6f9c42086040cad3daafdce3d27f3681f317f11..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/events.test.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - - -define([ - 'Magento_Ui/js/lib/registry/events' -], function (EventBus) { - 'use strict'; - - describe('Magento_Ui/js/lib/registry/events', function () { - var storage = { - has : function(){ - return false; - }, - get : function(){ - return []; - } - }, - eventsClass = new EventBus(storage); - - describe('"resolve" method', function () { - it('Check for defined ', function () { - expect(eventsClass.resolve()).toBeDefined(); - }); - it('Check answer type', function () { - var type = typeof(eventsClass.resolve()); - - expect(type).toEqual('object'); - }); - }); - describe('"wait" method', function () { - it('Check for defined ', function () { - expect(eventsClass.wait([],{})).toBeDefined(); - }); - it('Check return object property "requests" defined', function () { - var thisObject = eventsClass.wait([],{}).requests; - - expect(thisObject).toBeDefined(); - }); - it('Check return object property "requests" type', function () { - var thisObject = typeof(eventsClass.wait([],{}).requests); - - expect(thisObject).toEqual('object'); - }); - }); - describe('"_resolve" method', function () { - it('Check completion method', function () { - eventsClass.request = [{ - callback: function(){return true;}, - deps: {} - }]; - expect(eventsClass._resolve(0)).toEqual(false); - }); - }); - }); -}); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/registry.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/registry.test.js index fd814d0a68fd41a79daf74825595cbb402a6f752..a45deb1df23afc9263ad1476732ce58c912e6752 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/registry.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/registry.test.js @@ -38,13 +38,6 @@ define([ expect(type).toEqual('object'); }); - it('Check assigned value after used method', function () { - var elem = 'test', - prop = 'magento'; - - registry.set(elem, prop); - expect(registry.storage.data.get(elem)).toEqual(prop); - }); }); describe('"registry.get" method', function () { it('Check for defined', function () { @@ -63,14 +56,6 @@ define([ expect(type).toBeFalsy(); }); - it('Check called callback with arguments', function () { - var elems = ['magento'], - callback = function () {}; - - registry.events.wait = jasmine.createSpy(); - registry.get(elems, callback); - expect(registry.events.wait).toHaveBeenCalledWith(elems, callback); - }); }); describe('"registry.remove" method', function () { it('Check for defined', function () { @@ -89,13 +74,6 @@ define([ expect(type).toEqual('object'); }); - it('Check called registry.storage.remove with arguments', function () { - var elems = ['magento']; - - registry.storage.remove = jasmine.createSpy(); - registry.remove(elems); - expect(registry.storage.remove).toHaveBeenCalledWith(elems); - }); }); describe('"registry.has" method', function () { it('Check for defined', function () { @@ -106,24 +84,11 @@ define([ expect(type).toEqual('function'); }); - it('Check returned value if registry.storage has property', function () { - var name = 'magento'; - - registry.storage.data.set(name, 'magentoValue'); - expect(registry.has(name)).toEqual(true); - }); it('Check returned value if registry.storage has not property', function () { var name = 'magentoNonProperty'; expect(registry.has(name)).toEqual(false); }); - it('Check called registry.storage.has with arguments', function () { - var elems = ['magento']; - - registry.storage.has = jasmine.createSpy(); - registry.has(elems); - expect(registry.storage.has).toHaveBeenCalledWith(elems); - }); }); describe('"registry.async" method', function () { it('Check for defined', function () { @@ -149,22 +114,6 @@ define([ expect(type).toEqual('object'); }); - it('Check registry.storage for defined', function () { - registry.create(); - expect(registry.storage).toBeDefined(); - }); - it('Check registry.storage type', function () { - registry.create(); - expect(typeof registry.storage).toEqual('object'); - }); - it('Check registry.events for defined', function () { - registry.create(); - expect(registry.events).toBeDefined(); - }); - it('Check registry.events type', function () { - registry.create(); - expect(typeof registry.events).toEqual('object'); - }); }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/storage.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/storage.test.js deleted file mode 100644 index a2e2fd3c1206b79b1da01ebba2f4cdb248a25ed7..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/lib/registry/storage.test.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ - -/*eslint max-nested-callbacks: 0*/ - -define([ - 'Magento_Ui/js/lib/registry/storage' -], function (Storage) { - 'use strict'; - - describe('Magento_Ui/js/lib/registry/storage', function () { - var storage = new Storage(); - describe('"Storage constructor"', function () { - it('Check for defined', function () { - expect(storage).toBeDefined(); - }); - it('Check type', function () { - var type = typeof storage; - - expect(type).toEqual('object'); - }); - it('Check storage.data for defined', function () { - var data = storage.data; - - expect(typeof data).toEqual('object'); - }); - }); - describe('"storage.get" method', function () { - it('Check for defined', function () { - expect(storage.hasOwnProperty('get')).toBeDefined(); - }); - it('Check type', function () { - var type = typeof storage.get; - - expect(type).toEqual('function'); - }); - it('Check returned value if argument is array values', function () { - var elem = 'magento', - value = 'magentoValue'; - - storage.data.set(elem, value); - expect(storage.get([elem])).toEqual([value]); - }); - it('Check returned value if called withot arguments', function () { - expect(storage.get()).toEqual([]); - }); - }); - describe('"storage.set" method', function () { - it('Check for defined', function () { - expect(storage.hasOwnProperty('set')).toBeDefined(); - }); - it('Check type', function () { - var type = typeof storage.set; - - expect(type).toEqual('function'); - }); - it('Check returned value for defined', function () { - expect(storage.set()).toBeDefined(); - }); - it('Check returned value type', function () { - var type = typeof storage.set(); - - expect(type).toEqual('object'); - }); - it('Check returned value if argument is "elem, value" ', function () { - var elem = 'magento', - value = 'magentoValue'; - - storage.set(elem, value); - expect(storage.data.get(elem)).toEqual(value); - }); - }); - describe('"storage.remove" method', function () { - it('Check for defined', function () { - expect(storage.hasOwnProperty('remove')).toBeDefined(); - }); - it('Check type', function () { - var type = typeof storage.remove; - - expect(type).toEqual('function'); - }); - it('Check returned value for defined', function () { - expect(storage.remove([])).toBeDefined(); - }); - it('Check returned value type', function () { - var type = typeof storage.remove([]); - - expect(type).toEqual('object'); - }); - it('Check if called with argument "elem" ', function () { - var elem = 'magento', - value = 'magentoValue'; - - storage.data.set(elem, value); - storage.remove([elem]); - expect(storage.data.get(elem)).not.toBeDefined(); - }); - }); - describe('"storage.has" method', function () { - it('Check for defined', function () { - expect(storage.hasOwnProperty('has')).toBeDefined(); - }); - it('Check type', function () { - var type = typeof storage.has; - - expect(type).toEqual('function'); - }); - it('Check returned value if data has element property', function () { - var elem = 'magento', - value = 'magentoValue'; - - storage.data.set(elem, value); - expect(storage.has([elem])).toEqual(true); - }); - it('Check returned value if data has not element property', function () { - expect(storage.has(['value'])).toEqual(false); - }); - }); - }); -}); diff --git a/dev/tests/js/jasmine/tests/lib/mage/gallery.test.js b/dev/tests/js/jasmine/tests/lib/mage/gallery.test.js deleted file mode 100644 index 984bdf0cc3919ca35d55f82bd730aa129ea935e6..0000000000000000000000000000000000000000 --- a/dev/tests/js/jasmine/tests/lib/mage/gallery.test.js +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright © 2016 Magento. All rights reserved. - * See COPYING.txt for license details. - */ -define([ - 'jquery', - 'text!tests/assets/gallery/config.json', - 'mage/gallery/gallery', - 'magnifier/magnify' -], function ($, config, gallery, magnifier) { - - 'use strict'; - - var body = $('body'), - galleryAPI, - conf = JSON.parse(config), - gallerySelector = '[data-gallery-role="gallery"]', - magnifierSelector = '[data-gallery-role="magnifier"]', - stageSelector = '[data-gallery-role="stage-shaft"]', - navSelector = '[data-gallery-role="nav-frame"]', - dotSelector = '[data-nav-type="dot"]', - navWrap = '[data-gallery-role="nav-wrap"]', - dataToUpdate = [ - { - img: 'data:image/png;base64,' + - 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP4Xw8AAoABf5/NhYYAAAAASUVORK5CYII=' - }, { - img: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAw' + - 'CAAAAC0lEQVR42mP4Xw8AAoABf5/NhYYAAAAASUVORK5CYII=' - }, { - img: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAw' + - 'CAAAAC0lEQVR42mP4Xw8AAoABf5/NhYYAAAAASUVORK5CYII=' - }, { - img: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAw' + - 'CAAAAC0lEQVR42mP4Xw8AAoABf5/NhYYAAAAASUVORK5CYII=' - } - ], - waitsFor = function (test, fn) { - if (test()) { - fn(); - } else { - setTimeout(function () { - waitsFor(test, fn); - }, 10); - } - }; - - gallery(magnifier(conf, body), body); - - beforeEach(function () { - galleryAPI = $(gallerySelector).data('gallery'); - }); - - describe('magnifier/magnify', function () { - - if ('ontouchstart' in document.documentElement) { - it('magnifier is not initialized on mobile platforms', function () { - expect($(magnifierSelector).length).toBe(0); - }); - } else { - it('magnifier is defined', function () { - expect($(gallerySelector).magnify).toBeDefined(); - expect(typeof $(gallerySelector).magnify).toBe('function'); - expect($(magnifierSelector).hasClass('hidden')).toBeTruthy(); - }); - - it('magnifier is initialized on desktop platforms', function () { - expect($(magnifierSelector + ' img').attr('src')).toBe($(stageSelector + ' img').attr('src')); - }); - it('magnifier appearing on event on desktop platforms', function () { - var ev = conf.magnifierOpts.eventType === 'click' ? 'click' : 'mouseover'; - expect($($(magnifierSelector).children()[0]).hasClass('magnifier-large hidden')).toBeTruthy(); - $(stageSelector + ' img').trigger(ev); - expect($($(magnifierSelector).children()[0]).hasClass('magnifier-large hidden')).toBeFalsy(); - $(stageSelector + ' img').trigger('mouseleave'); - }); - } - }); - - describe('mage/gallery/gallery', function () { - - it('gallery loaded', function () { - expect($(navSelector).length).toBe(conf.data.length); - }); - - it('show last', function () { - galleryAPI.last(); - expect($(navSelector + ':eq(' + (conf.data.length - 1) + ')') - .attr('data-active') === 'true').toBeTruthy(); - }); - - it('show first', function () { - galleryAPI.first(); - expect($(navSelector + ':eq(0)').attr('data-active') === 'true').toBeTruthy(); - }); - - it('show next', function () { - galleryAPI.next(); - expect($(navSelector + ':eq(1)').attr('data-active') === 'true').toBeTruthy(); - }); - - it('show previous', function () { - galleryAPI.prev(); - expect($(navSelector + ':eq(0)').attr('data-active') === 'true').toBeTruthy(); - }); - - it('show by number', function () { - galleryAPI.seek(3); - expect($(navSelector + ':eq(2)').attr('data-active') === 'true').toBeTruthy(); - }); - - it('update options', function () { - expect($(navSelector).attr('data-nav-type') === 'thumb').toBeTruthy(); - galleryAPI.updateOptions({ - nav: 'dots' - }); - expect($(dotSelector).length).toBe(conf.data.length); - }); - - it('update data', function () { - galleryAPI.updateData(dataToUpdate); - expect($(dotSelector).length).toBe(dataToUpdate.length); - }); - - it('breakpoints override configs', function () { - expect($('.fotorama__arr').css('display')).toBe('block'); - }); - - it('fullscreen enter', function (done) { - expect($(navWrap).css('display') === 'block').toBeTruthy(); - galleryAPI.fotorama.requestFullScreen(); - - waitsFor(function () { - return $(navWrap).css('display') !== 'block'; - }, function () { - expect($(navWrap).css('display') === 'none').toBeTruthy(); - done(); - }); - }); - - it('fullscreen exit', function (done) { - expect($(navWrap).css('display') === 'none').toBeTruthy(); - galleryAPI.fotorama.cancelFullScreen(); - - waitsFor(function () { - return $(navWrap).css('display') !== 'none'; - }, function () { - expect($(navWrap).css('display') === 'block').toBeTruthy(); - done(); - }); - }); - - }); -}); diff --git a/dev/tests/static/get_github_changes.php b/dev/tests/static/get_github_changes.php index c142aae18d8ecd5a4ab3fa895e32e5510b71683b..f332208cd17d0861dd1b28bd7096a0b3fe0066a5 100644 --- a/dev/tests/static/get_github_changes.php +++ b/dev/tests/static/get_github_changes.php @@ -12,9 +12,9 @@ // @codingStandardsIgnoreFile define( -'USAGE', -<<<USAGE - php -f get_github_changes.php -- + 'USAGE', + <<<USAGE + php -f get_github_changes.php -- --output-file="<output_file>" --base-path="<base_path>" --repo="<main_repo>" @@ -36,6 +36,8 @@ $fileExtensions = explode(',', isset($options['file-extensions']) ? $options['fi $mainline = 'mainline_' . (string)rand(0, 9999); $repo = getRepo($options, $mainline); +$branches = $repo->getBranches('--remotes'); +generateBranchesList($options['output-file'], $branches, $options['branch']); $changes = retrieveChangesAcrossForks($mainline, $repo, $options['branch']); $changedFiles = getChangedFiles($changes, $fileExtensions); generateChangedFilesList($options['output-file'], $changedFiles); @@ -57,6 +59,25 @@ function generateChangedFilesList($outputFile, $changedFiles) fclose($changedFilesList); } +/** + * Generates a file containing origin branches + * + * @param string $outputFile + * @param array $branches + * @param string $branchName + * @return void + */ +function generateBranchesList($outputFile, $branches, $branchName) +{ + $branchOutputFile = str_replace('changed_files', 'branches', $outputFile); + $branchesList = fopen($branchOutputFile, 'w'); + fwrite($branchesList, $branchName . PHP_EOL); + foreach ($branches as $branch) { + fwrite($branchesList, substr(strrchr($branch, '/'), 1) . PHP_EOL); + } + fclose($branchesList); +} + /** * Gets list of changed files * @@ -84,7 +105,7 @@ function getChangedFiles(array $changes, array $fileExtensions) * * @param array $options * @param string $mainline - * @return array + * @return GitRepo * @throws Exception */ function getRepo($options, $mainline) @@ -203,6 +224,19 @@ class GitRepo $this->call(sprintf('fetch %s', $remoteAlias)); } + /** + * Returns branches + * + * @param string $source + * @return array|mixed + */ + public function getBranches($source = '--all') + { + $result = $this->call(sprintf('branch ' . $source)); + + return is_array($result) ? $result : []; + } + /** * Returns files changes between branch and HEAD * diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php index 52244463a535693746eddf8f4e0d07b05cf9de08..ff12618b399639fb8c4f56a422828cbb7c0c1e67 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Magento/Framework/Search/RequestConfigTest.php @@ -100,7 +100,7 @@ Element 'filterReference': The attribute 'clause' is required but missing. Element 'filterReference': The attribute 'ref' is required but missing. Element 'filter': The attribute 'field' is required but missing. Element 'metric', attribute 'type': [facet 'enumeration'] " . - "The value 'sumasdasd' is not an element of the set {'sum', 'count', 'min', 'max'}. + "The value 'sumasdasd' is not an element of the set {'sum', 'count', 'min', 'max', 'avg'}. Element 'metric', attribute 'type': 'sumasdasd' is not a valid value of the local atomic type. Element 'bucket': Missing child element(s). Expected is one of ( metrics, ranges ). Element 'request': Missing child element(s). Expected is ( from )." diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/reference.txt b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/reference.txt index 836a928dc5a72cf6a517dc1ae7f1bbc61e62a763..e4078a959c7a89284e508a884229f69d80e2e25a 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/reference.txt +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/blacklist/reference.txt @@ -115,3 +115,4 @@ DoubleColon \Magento\TestModuleMessageQueueConfiguration\AsyncHandler \Magento\TestModuleMessageQueueConfiguration\SyncHandler \Magento\TestModuleAsyncAmqp\Model\AsyncTestData +\Magento\Mtf\Client\ElementInterface diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php b/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php index 87bd329ef3b5611475c6ee9486cdc9f27c4ea5fe..2f3d312fb41ffba1287bafd324a64ac751316d5c 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/ModuleDBChangeTest.php @@ -15,38 +15,49 @@ class ModuleDBChangeTest extends \PHPUnit_Framework_TestCase /** * @var string */ - protected static $changedFilesPattern = __DIR__ . '/../_files/changed_files*'; + private static $branchesFilesPattern = __DIR__ . '/../_files/branches*'; /** * @var string */ - protected static $changedFileList = ''; + private static $changedFilesPattern = __DIR__ . '/../_files/changed_files*'; /** - * @var string Path for Magento's composer.json + * @var string */ - protected static $composerFilePath = BP . '/composer.json'; + private static $changedFileList = ''; /** - * @var bool Is tests executes on develop branch + * @var bool */ - protected static $isOnDevVersion = false; + private static $actualBranch = false; /** * Set changed files paths and list for all projects */ public static function setUpBeforeClass() { - foreach (glob(self::$changedFilesPattern) as $changedFile) { - self::$changedFileList .= file_get_contents($changedFile) . PHP_EOL; - } + foreach (glob(self::$branchesFilesPattern) as $branchesFile) { + //get the current branchname from the first line + $branchName = trim(file($branchesFile)[0]); + if ($branchName === 'develop') { + self::$actualBranch = true; + } else { + //get current minor branch name + preg_match('|^(\d+\.\d+)|', $branchName, $minorBranch); + $branchName = $minorBranch[0]; + + //get all version branches + preg_match_all('|^(\d+\.\d+)|m', file_get_contents($branchesFile), $matches); - if (file_exists(self::$composerFilePath)) { - $jsonData = json_decode(file_get_contents(self::$composerFilePath)); - if (substr((string) $jsonData->version, -4) == '-dev') { - self::$isOnDevVersion = true; + //check is this a latest release branch + self::$actualBranch = ($branchName == max($matches[0])); } } + + foreach (glob(self::$changedFilesPattern) as $changedFile) { + self::$changedFileList .= file_get_contents($changedFile) . PHP_EOL; + } } /** @@ -54,15 +65,14 @@ class ModuleDBChangeTest extends \PHPUnit_Framework_TestCase */ public function testModuleXmlFiles() { - if (self::$isOnDevVersion) { - $this->markTestSkipped('This test isn\'t applicable to the developer version of Magento'); + if (!self::$actualBranch) { + preg_match_all('|etc/module\.xml$|mi', self::$changedFileList, $matches); + $this->assertEmpty( + reset($matches), + 'module.xml changes for patch releases in non-actual branches are not allowed:' . PHP_EOL . + implode(PHP_EOL, array_values(reset($matches))) + ); } - preg_match_all('|etc/module\.xml$|mi', self::$changedFileList, $matches); - $this->assertEmpty( - reset($matches), - 'module.xml changes for patch releases in non-actual branches are not allowed:' . PHP_EOL . - implode(PHP_EOL, array_values(reset($matches))) - ); } /** @@ -70,14 +80,13 @@ class ModuleDBChangeTest extends \PHPUnit_Framework_TestCase */ public function testModuleSetupFiles() { - if (self::$isOnDevVersion) { - $this->markTestSkipped('This test isn\'t applicable to the developer version of Magento'); + if (!self::$actualBranch) { + preg_match_all('|app/code/Magento/[^/]+/Setup/[^/]+$|mi', self::$changedFileList, $matches); + $this->assertEmpty( + reset($matches), + 'Code with changes for DB schema or data in non-actual branches are not allowed:' . PHP_EOL . + implode(PHP_EOL, array_values(reset($matches))) + ); } - preg_match_all('|app/code/Magento/[^/]+/Setup/[^/]+$|mi', self::$changedFileList, $matches); - $this->assertEmpty( - reset($matches), - 'Code with changes for DB schema or data in non-actual branches are not allowed:' . PHP_EOL . - implode(PHP_EOL, array_values(reset($matches))) - ); } } diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php index bc49ab15a7d54151759f7e183fb36a8a7b6571c6..2ce1bf02bf3896da26a633c158b77697876c4d02 100644 --- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_methods.php @@ -2155,7 +2155,6 @@ return [ ['addOrderedQty', 'Magento\Reports\Model\ResourceModel\Product\Collection'], ['prepareForProductsInCarts', 'Magento\Reports\Model\ResourceModel\Quote\Collection'], ['getOrdersSubSelect', 'Magento\Reports\Model\ResourceModel\Quote\Collection'], - ['isOrderIncrementIdUsed', 'Magento\Quote\Model\ResourceModel\Quote'], ['isStateProtected', 'Magento\Sales\Model\Order'], ['_getBundleOptions', 'Magento\Bundle\Block\Checkout\Cart\Item\Renderer'], ['_getSelectionFinalPrice', 'Magento\Bundle\Block\Checkout\Cart\Item\Renderer'], diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index cfbeb4f54c9e29d2f7b320b57c51ae7a7b31a676..9af29ab2c458d8a8b09a73e570695d133bd7a123 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -341,6 +341,9 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface /** * Creates a PDO object and connects to the database. * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * * @return void * @throws \Zend_Db_Adapter_Exception */ @@ -371,6 +374,10 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface list($this->_config['host'], $this->_config['port']) = explode(':', $this->_config['host']); } + if (!isset($this->_config['driver_options'][\PDO::MYSQL_ATTR_MULTI_STATEMENTS])) { + $this->_config['driver_options'][\PDO::MYSQL_ATTR_MULTI_STATEMENTS] = false; + } + $this->logger->startTimer(); parent::_connect(); $this->logger->logStats(LoggerInterface::TYPE_CONNECT, ''); @@ -562,6 +569,7 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface * @throws \Zend_Db_Adapter_Exception To re-throw \PDOException. * @throws LocalizedException In case multiple queries are attempted at once, to protect from SQL injection * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @deprecated */ public function multiQuery($sql, $bind = []) { @@ -728,6 +736,8 @@ class Mysql extends \Zend_Db_Adapter_Pdo_Mysql implements AdapterInterface * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) + + * @deprecated */ protected function _splitMultiQuery($sql) { diff --git a/lib/internal/Magento/Framework/Filter/Input/MaliciousCode.php b/lib/internal/Magento/Framework/Filter/Input/MaliciousCode.php index 10f1289b90c1841c7e5c9c014b3943132ab8e5f3..ad4746a36513c4dd028b1c81d27dbbcec70148eb 100644 --- a/lib/internal/Magento/Framework/Filter/Input/MaliciousCode.php +++ b/lib/internal/Magento/Framework/Filter/Input/MaliciousCode.php @@ -31,7 +31,7 @@ class MaliciousCode implements \Zend_Filter_Interface //js attributes '/(ondblclick|onclick|onkeydown|onkeypress|onkeyup|onmousedown|onmousemove|onmouseout|onmouseover|onmouseup|onload|onunload|onerror)=[^<]*(?=\/*\>)/Uis', //tags - '/<\/?(script|meta|link|frame|iframe).*>/Uis', + '/<\/?(script|meta|link|frame|iframe|object).*>/Uis', //base64 usage '/src=[^<]*base64[^<]*(?=\/*\>)/Uis', ]; diff --git a/lib/internal/Magento/Framework/Filter/Test/Unit/Input/MaliciousCodeTest.php b/lib/internal/Magento/Framework/Filter/Test/Unit/Input/MaliciousCodeTest.php index 512d8e89750bc24c666e58237668ad63f7e440b8..93de72e3c57c5610882aece936c9346be40dea64 100644 --- a/lib/internal/Magento/Framework/Filter/Test/Unit/Input/MaliciousCodeTest.php +++ b/lib/internal/Magento/Framework/Filter/Test/Unit/Input/MaliciousCodeTest.php @@ -89,6 +89,7 @@ class MaliciousCodeTest extends \PHPUnit_Framework_TestCase 'Tag is removed <link>SomeLink</link>', 'Tag is removed <frame>SomeFrame</frame>', 'Tag is removed <iframe>SomeIFrame</iframe>', + 'Tag is removed <object>SomeObject</object>', ], [ 'Tag is removed SomeScript', @@ -96,6 +97,7 @@ class MaliciousCodeTest extends \PHPUnit_Framework_TestCase 'Tag is removed SomeLink', 'Tag is removed SomeFrame', 'Tag is removed SomeIFrame', + 'Tag is removed SomeObject', ], ], 'Base64' => [ diff --git a/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php b/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php index fb00e9f59c83097b13274e47e7b0a7a52634ecc2..181783c7b2115d8a216c681077e116e98344d15c 100644 --- a/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php +++ b/lib/internal/Magento/Framework/HTTP/Adapter/Curl.php @@ -20,7 +20,15 @@ class Curl implements \Zend_Http_Client_Adapter_Interface * * @var array */ - protected $_config = []; + protected $_config = [ + 'protocols' => (CURLPROTO_HTTP + | CURLPROTO_HTTPS + | CURLPROTO_FTP + | CURLPROTO_FTPS + ), + 'verifypeer' => true, + 'verifyhost' => 2, + ]; /** * Curl handle @@ -41,7 +49,10 @@ class Curl implements \Zend_Http_Client_Adapter_Interface 'ssl_cert' => CURLOPT_SSLCERT, 'userpwd' => CURLOPT_USERPWD, 'useragent' => CURLOPT_USERAGENT, - 'referer' => CURLOPT_REFERER + 'referer' => CURLOPT_REFERER, + 'protocols' => CURLOPT_PROTOCOLS, + 'verifypeer' => CURLOPT_SSL_VERIFYPEER, + 'verifyhost' => CURLOPT_SSL_VERIFYHOST, ]; /** @@ -55,8 +66,6 @@ class Curl implements \Zend_Http_Client_Adapter_Interface * Apply current configuration array to transport resource * * @return \Magento\Framework\HTTP\Adapter\Curl - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ protected function _applyConfig() { @@ -65,22 +74,28 @@ class Curl implements \Zend_Http_Client_Adapter_Interface curl_setopt($this->_getResource(), $option, $value); } - if (empty($this->_config)) { - return $this; + // apply config options + foreach ($this->getDefaultConfig() as $option => $value) { + curl_setopt($this->_getResource(), $option, $value); } - $verifyPeer = isset($this->_config['verifypeer']) ? $this->_config['verifypeer'] : true; - curl_setopt($this->_getResource(), CURLOPT_SSL_VERIFYPEER, $verifyPeer); - - $verifyHost = isset($this->_config['verifyhost']) ? $this->_config['verifyhost'] : 2; - curl_setopt($this->_getResource(), CURLOPT_SSL_VERIFYHOST, $verifyHost); + return $this; + } - foreach ($this->_config as $param => $curlOption) { + /** + * Get default options + * + * @return array + */ + private function getDefaultConfig() + { + $config = []; + foreach (array_keys($this->_config) as $param) { if (array_key_exists($param, $this->_allowedParams)) { - curl_setopt($this->_getResource(), $this->_allowedParams[$param], $this->_config[$param]); + $config[$this->_allowedParams[$param]] = $this->_config[$param]; } } - return $this; + return $config; } /** @@ -116,7 +131,9 @@ class Curl implements \Zend_Http_Client_Adapter_Interface */ public function setConfig($config = []) { - $this->_config = $config; + foreach ($config as $key => $value) { + $this->_config[$key] = $value; + } return $this; } @@ -268,6 +285,13 @@ class Curl implements \Zend_Http_Client_Adapter_Interface $multihandle = curl_multi_init(); + // add default parameters + foreach ($this->getDefaultConfig() as $defaultOption => $defaultValue) { + if (!isset($options[$defaultOption])) { + $options[$defaultOption] = $defaultValue; + } + } + foreach ($urls as $key => $url) { $handles[$key] = curl_init(); curl_setopt($handles[$key], CURLOPT_URL, $url); diff --git a/lib/internal/Magento/Framework/HTTP/Test/Unit/Adapter/CurlTest.php b/lib/internal/Magento/Framework/HTTP/Test/Unit/Adapter/CurlTest.php index 255be0a5596a622151e9e1091841d4f5ff8bf58b..37cd33d18683062ab48d16ae8aae2733def7a75e 100644 --- a/lib/internal/Magento/Framework/HTTP/Test/Unit/Adapter/CurlTest.php +++ b/lib/internal/Magento/Framework/HTTP/Test/Unit/Adapter/CurlTest.php @@ -10,10 +10,14 @@ use \Magento\Framework\HTTP\Adapter\Curl; class CurlTest extends \PHPUnit_Framework_TestCase { - /** @var Curl */ + /** + * @var Curl + */ protected $model; - /** @var \Closure */ + /** + * @var \Closure + */ public static $curlExectClosure; protected function setUp() @@ -42,4 +46,3 @@ class CurlTest extends \PHPUnit_Framework_TestCase ]; } } - diff --git a/lib/internal/Magento/Framework/ObjectManager/Config/Compiled.php b/lib/internal/Magento/Framework/ObjectManager/Config/Compiled.php index a3ed3b941844eec7497eb233d1c43e74045c45e1..779e8cf0a0e5ecf15b0fcb6da1e48b80a5bb3072 100644 --- a/lib/internal/Magento/Framework/ObjectManager/Config/Compiled.php +++ b/lib/internal/Magento/Framework/ObjectManager/Config/Compiled.php @@ -1,15 +1,15 @@ <?php /** - * * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Framework\ObjectManager\Config; +use Magento\Framework\ObjectManager\ConfigInterface; use Magento\Framework\ObjectManager\ConfigCacheInterface; use Magento\Framework\ObjectManager\RelationsInterface; -class Compiled implements \Magento\Framework\ObjectManager\ConfigInterface +class Compiled implements ConfigInterface { /** * @var array @@ -129,9 +129,15 @@ class Compiled implements \Magento\Framework\ObjectManager\ConfigInterface */ public function extend(array $configuration) { - $this->arguments = $configuration['arguments']; - $this->virtualTypes = $configuration['instanceTypes']; - $this->preferences = $configuration['preferences']; + $this->arguments = isset($configuration['arguments']) + ? array_replace($this->arguments, $configuration['arguments']) + : $this->arguments; + $this->virtualTypes = isset($configuration['instanceTypes']) + ? array_replace($this->virtualTypes, $configuration['instanceTypes']) + : $this->virtualTypes; + $this->preferences = isset($configuration['preferences']) + ? array_replace($this->preferences, $configuration['preferences']) + : $this->preferences; } /** diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php index 8ddb6255b9b862a37dea1aaefd742a4c03923d24..3d280f1224b8f3bb2e9082436fa7524308730387 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php @@ -71,6 +71,7 @@ class Adapter implements AdapterInterface /** * {@inheritdoc} + * @throws \LogicException */ public function query(RequestInterface $request) { diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Metrics.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Metrics.php index 5860591eaf702494614ec89a90e816e159ffeb12..cf544eb126e1195096603979bd7378e08f44730d 100644 --- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Metrics.php +++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Aggregation/Builder/Metrics.php @@ -14,7 +14,7 @@ class Metrics * * @var string[] */ - private $mapMetrics = ['count', 'sum', 'min', 'max', 'avg']; + private $allowedMetrics = ['count', 'sum', 'min', 'max', 'avg']; /** * Build metrics for Select->columns @@ -30,7 +30,7 @@ class Metrics foreach ($metrics as $metric) { $metricType = $metric->getType(); - if (in_array($metricType, $this->mapMetrics)) { + if (in_array($metricType, $this->allowedMetrics, true)) { $selectAggregations[$metricType] = "$metricType(main_table.value)"; } } diff --git a/lib/internal/Magento/Framework/Search/etc/requests.xsd b/lib/internal/Magento/Framework/Search/etc/requests.xsd index f185699c5a5e890757850fa8985b2e0400c56c35..294232513b7d2ec1938e243423a3113e151fd0c0 100644 --- a/lib/internal/Magento/Framework/Search/etc/requests.xsd +++ b/lib/internal/Magento/Framework/Search/etc/requests.xsd @@ -263,6 +263,7 @@ <xs:enumeration value="count" /> <xs:enumeration value="min" /> <xs:enumeration value="max" /> + <xs:enumeration value="avg" /> </xs:restriction> </xs:simpleType> </xs:attribute> diff --git a/lib/internal/Magento/Framework/Session/SessionManager.php b/lib/internal/Magento/Framework/Session/SessionManager.php index e8013b024f4384e2c9b2261daed97a2ccbfb7f03..a517f1fd0b0b744cd44bad6b78ddd9c687a1fab5 100644 --- a/lib/internal/Magento/Framework/Session/SessionManager.php +++ b/lib/internal/Magento/Framework/Session/SessionManager.php @@ -298,6 +298,7 @@ class SessionManager implements SessionManagerInterface return; } + session_regenerate_id(true); session_destroy(); if ($options['send_expire_cookie']) { $this->expireSessionCookie(); diff --git a/lib/internal/Magento/Framework/Test/Unit/ObjectManager/Config/CompiledTest.php b/lib/internal/Magento/Framework/Test/Unit/ObjectManager/Config/CompiledTest.php new file mode 100644 index 0000000000000000000000000000000000000000..3dd9f5065a1421ec822d4a81377ea81a5c3f8f52 --- /dev/null +++ b/lib/internal/Magento/Framework/Test/Unit/ObjectManager/Config/CompiledTest.php @@ -0,0 +1,91 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Test\Unit\ObjectManager\Config; + +use Magento\Framework\ObjectManager\Config\Compiled as CompiledConfig; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class CompiledTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var ObjectManagerHelper + */ + private $objectManagerHelper; + + protected function setUp() + { + $this->objectManagerHelper = new ObjectManagerHelper($this); + } + + /** + * @param array $initialData + * @param array $configuration + * @param array $expectedArguments + * @param array $expectedVirtualTypes + * @param array $expectedPreferences + * + * @dataProvider extendDataProvider + */ + public function testExtend( + array $initialData, + array $configuration, + array $expectedArguments, + array $expectedVirtualTypes, + array $expectedPreferences + ) { + /** @var CompiledConfig $compiledConfig */ + $compiledConfig = $this->objectManagerHelper->getObject(CompiledConfig::class, ['data' => $initialData]); + $compiledConfig->extend($configuration); + + foreach ($expectedArguments as $type => $arguments) { + $this->assertEquals($arguments, $compiledConfig->getArguments($type)); + } + + $this->assertEquals($expectedVirtualTypes, $compiledConfig->getVirtualTypes()); + $this->assertEquals($expectedPreferences, $compiledConfig->getPreferences()); + } + + /** + * @return array + */ + public function extendDataProvider() + { + return [ + [ + 'initialData' => [ + 'arguments' => [ + 'type1' => serialize(['argument1_1' => 'argumentValue1_1', 'argument1_2' => 'argumentValue1_2']) + ], + 'instanceTypes' => [ + 'instanceType1' => 'instanceTypeValue1', 'instanceType2' => 'instanceTypeValue2' + ], + 'preferences' => ['preference1' => 'preferenceValue1', 'preference2' => 'preferenceValue2'] + ], + 'configuration' => [ + 'arguments' => [ + 'type1' => serialize(['argument1_1' => 'newArgumentValue1_1']), + 'type2' => serialize(['argument2_1' => 'newArgumentValue2_1']) + ], + 'instanceTypes' => [ + 'instanceType2' => 'newInstanceTypeValue2', 'instanceType3' => 'newInstanceTypeValue3' + ], + 'preferences' => ['preference1' => 'newPreferenceValue1'] + ], + 'expectedArguments' => [ + 'type1' => ['argument1_1' => 'newArgumentValue1_1'], + 'type2' => ['argument2_1' => 'newArgumentValue2_1'] + ], + 'expectedVirtualTypes' => [ + 'instanceType1' => 'instanceTypeValue1', 'instanceType2' => 'newInstanceTypeValue2', + 'instanceType3' => 'newInstanceTypeValue3' + ], + 'expectedPreferences' => [ + 'preference1' => 'newPreferenceValue1', 'preference2' => 'preferenceValue2' + ] + ] + ]; + } +} diff --git a/lib/internal/Magento/Framework/Validator/AllowedProtocols.php b/lib/internal/Magento/Framework/Validator/AllowedProtocols.php new file mode 100644 index 0000000000000000000000000000000000000000..3c7bbb3d997236aae0e7cc91994505af78620957 --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/AllowedProtocols.php @@ -0,0 +1,59 @@ +<?php +/** + * Protocol validator + * + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Validator; + +use \Zend\Uri\Uri; + +/** + * Check is URI starts from allowed protocol + * + * Class AllowedProtocols + * @package Magento\Framework\Validator + */ +class AllowedProtocols extends AbstractValidator +{ + /** + * List of supported protocols + * + * @var array + */ + private $listOfProtocols = [ + 'http', + 'https', + ]; + + /** + * Constructor. + * @param array $listOfProtocols + */ + public function __construct($listOfProtocols = []) + { + if (count($listOfProtocols)) { + $this->listOfProtocols = $listOfProtocols; + } + } + + /** + * Validate URI + * + * @param string $value + * @return bool + */ + public function isValid($value) + { + $uri = new Uri($value); + $isValid = in_array( + strtolower($uri->getScheme()), + $this->listOfProtocols + ); + if (!$isValid) { + $this->_addMessages(["Protocol isn't allowed"]); + } + return $isValid; + } +} diff --git a/lib/internal/Magento/Framework/Validator/Test/Unit/UrlTest.php b/lib/internal/Magento/Framework/Validator/Test/Unit/UrlTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d66f84289424920d3d8aca1e5b39e76fa1ad1503 --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/Test/Unit/UrlTest.php @@ -0,0 +1,76 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Validator\Test\Unit; + +use Magento\Framework\Validator\Url as UrlValidator; + +class UrlTest extends \PHPUnit_Framework_TestCase +{ + /** + * @var UrlValidator + */ + private $validator; + + protected function setUp() + { + $this->validator = new UrlValidator(); + } + + /** + * @param array $allowedSchemes + * @param string $url + * @param bool $expectedResult + * @dataProvider isValidDataProvider + */ + public function testIsValid(array $allowedSchemes, $url, $expectedResult) + { + $this->assertSame($expectedResult, $this->validator->isValid($url, $allowedSchemes)); + } + + /** + * @return array + */ + public function isValidDataProvider() + { + return [ + [ + 'allowedSchemes' => [], + 'url' => 'http://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => ['http'], + 'url' => 'http://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => [], + 'url' => 'https://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => ['https'], + 'url' => 'https://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => [], + 'url' => 'http://example.com_test', + 'expectedResult' => false, + ], + [ + 'allowedSchemes' => [], + 'url' => 'ftp://example.com', + 'expectedResult' => true, + ], + [ + 'allowedSchemes' => ['ftp'], + 'url' => 'ftp://example.com', + 'expectedResult' => true, + ], + ]; + } +} diff --git a/lib/internal/Magento/Framework/Validator/Url.php b/lib/internal/Magento/Framework/Validator/Url.php new file mode 100644 index 0000000000000000000000000000000000000000..27262009b2d200bc354947208bfd075898a3ac0e --- /dev/null +++ b/lib/internal/Magento/Framework/Validator/Url.php @@ -0,0 +1,37 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Framework\Validator; + +/** + * Class Url validates URL and checks that it has allowed scheme + */ +class Url +{ + /** + * Validate URL and check that it has allowed scheme + * + * @param string $value + * @param array $allowedSchemes + * @return bool + */ + public function isValid($value, array $allowedSchemes = []) + { + $isValid = true; + + if (!filter_var($value, FILTER_VALIDATE_URL)) { + $isValid = false; + } + + if ($isValid && !empty($allowedSchemes)) { + $url = parse_url($value); + if (empty($url['scheme']) || !in_array($url['scheme'], $allowedSchemes)) { + $isValid = false; + } + } + + return $isValid; + } +} diff --git a/lib/internal/Magento/Framework/View/Layout/Reader/Block.php b/lib/internal/Magento/Framework/View/Layout/Reader/Block.php index 9831c52a4656310e99c424ce7cc7dd8cd17f68fe..ff5ade125521acff54a182cea51e7f34985292d0 100755 --- a/lib/internal/Magento/Framework/View/Layout/Reader/Block.php +++ b/lib/internal/Magento/Framework/View/Layout/Reader/Block.php @@ -207,13 +207,15 @@ class Block implements Layout\ReaderInterface $elementRemove = filter_var($currentElement->getAttribute('remove'), FILTER_VALIDATE_BOOLEAN); if ($elementRemove) { $scheduledStructure->setElementToRemoveList($elementName); - } else { - $data = $scheduledStructure->getStructureElementData($elementName, []); - $data['attributes'] = $this->mergeBlockAttributes($data, $currentElement); - $this->updateScheduledData($currentElement, $data); - $this->evaluateArguments($currentElement, $data); - $scheduledStructure->setStructureElementData($elementName, $data); + return; + } elseif ($currentElement->getAttribute('remove')) { + $scheduledStructure->unsetElementFromListToRemove($elementName); } + $data = $scheduledStructure->getStructureElementData($elementName, []); + $data['attributes'] = $this->mergeBlockAttributes($data, $currentElement); + $this->updateScheduledData($currentElement, $data); + $this->evaluateArguments($currentElement, $data); + $scheduledStructure->setStructureElementData($elementName, $data); } /** diff --git a/lib/internal/Magento/Framework/View/Layout/Reader/Container.php b/lib/internal/Magento/Framework/View/Layout/Reader/Container.php index c4111a13c33b37fe7e5fcc202ec0773928a1844d..cd4956447a3b14a305e0ca90614633def896a6c7 100755 --- a/lib/internal/Magento/Framework/View/Layout/Reader/Container.php +++ b/lib/internal/Magento/Framework/View/Layout/Reader/Container.php @@ -141,11 +141,12 @@ class Container implements Layout\ReaderInterface ) { $containerName = $currentElement->getAttribute('name'); $containerRemove = filter_var($currentElement->getAttribute('remove'), FILTER_VALIDATE_BOOLEAN); - if ($containerRemove) { $scheduledStructure->setElementToRemoveList($containerName); - } else { - $this->mergeContainerAttributes($scheduledStructure, $currentElement); + return; + } elseif ($currentElement->getAttribute('remove')) { + $scheduledStructure->unsetElementFromListToRemove($containerName); } + $this->mergeContainerAttributes($scheduledStructure, $currentElement); } } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/BlockTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/BlockTest.php index 24bbd9391807463226bbe5c276df9aca4c31fd59..8cf049f56f1743484f0d559e4f8abf0caaa6b5f3 100755 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/BlockTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/BlockTest.php @@ -188,6 +188,12 @@ class BlockTest extends \PHPUnit_Framework_TestCase $setCondition, $setRemoveCondition ) { + if ($literal == 'referenceBlock' && $remove == 'false') { + $this->scheduledStructure->expects($this->once()) + ->method('unsetElementFromListToRemove') + ->with($literal); + } + $this->context->expects($this->once())->method('getScheduledStructure') ->will($this->returnValue($this->scheduledStructure)); diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/ContainerTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/ContainerTest.php index 4e10a5a6a08415ca9d06cb95f5e10fa3a9361c2e..198a70d2121f16962471d3e5f5587a1e89130c3f 100755 --- a/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/ContainerTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Layout/Reader/ContainerTest.php @@ -102,6 +102,12 @@ class ContainerTest extends \PHPUnit_Framework_TestCase ->with($contextMock, $elementCurrent) ->willReturnSelf(); + if ($elementCurrent->getAttribute('remove') == 'false') { + $scheduledStructureMock->expects($this->once()) + ->method('unsetElementFromListToRemove') + ->with($elementCurrent->getAttribute('name')); + } + $this->container->interpret($contextMock, $elementCurrent); } diff --git a/lib/internal/Magento/Framework/composer.json b/lib/internal/Magento/Framework/composer.json index 98338ed4b099d202ce7d8e482b2bda9e3a675068..926d4cc54c140efb315921432b8afc5ebd237cf7 100644 --- a/lib/internal/Magento/Framework/composer.json +++ b/lib/internal/Magento/Framework/composer.json @@ -8,7 +8,7 @@ "AFL-3.0" ], "require": { - "php": "~5.6.0|7.0.2|7.0.4|~7.0.6", + "php": "~5.6.5|7.0.2|7.0.4|~7.0.6", "ext-spl": "*", "ext-dom": "*", "ext-simplexml": "*", diff --git a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js index 66b72857d003397b8a756dc6aae9c0de70c58eeb..2c4ab52b896cb8fd8bf01974d7f39cfc8f11e4f4 100755 --- a/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js +++ b/lib/web/mage/adminhtml/wysiwyg/tiny_mce/setup.js @@ -53,6 +53,10 @@ define([ }); } + if (jQuery.isReady) { + tinyMCE.dom.Event.domLoaded = true; + } + tinyMCE.init(this.getSettings(mode)); }, @@ -78,6 +82,7 @@ define([ } var settings = { + entity_encoding: 'raw', mode: (mode != undefined ? mode : 'none'), elements: this.id, theme: 'advanced', @@ -357,6 +362,8 @@ define([ // escape special chars in directives url to use it in regular expression var url = this.makeDirectiveUrl('%directive%').replace(/([$^.?*!+:=()\[\]{}|\\])/g, '\\$1'); var reg = new RegExp(url.replace('%directive%', '([a-zA-Z0-9,_-]+)')); + content = decodeURIComponent(content); + return content.gsub(reg, function(match) { return Base64.mageDecode(match[1]); }.bind(this)); diff --git a/lib/web/mage/validation.js b/lib/web/mage/validation.js index 3041a177185cf1dfa5bda6e233a2c58a0282d0de..eed9c676b4c90ce1a944d9e7c5ce88a815af08cf 100644 --- a/lib/web/mage/validation.js +++ b/lib/web/mage/validation.js @@ -1441,17 +1441,19 @@ * Validate single element. * * @param {Element} element + * @param {Object} config * @returns {*} */ - $.validator.validateSingleElement = function (element) { + $.validator.validateSingleElement = function (element, config) { var errors = {}, valid = true, validateConfig = { errorElement: 'label', ignore: '.ignore-validate' }, - form, validator, classes; + form, validator, classes, elementValue; + $.extend(validateConfig, config); element = $(element).not(validateConfig.ignore); if (!element.length) { @@ -1475,7 +1477,11 @@ validator.toShow = validator.toHide = $([]); $.each(classes, $.proxy(function (i, className) { - if (this.methods[className] && !this.methods[className](element.val(), element.get(0))) { + elementValue = element.val(); + if (element.is(':checkbox') || element.is(':radio')) { + elementValue = element.is(':checked') || null; + } + if (this.methods[className] && !this.methods[className](elementValue, element.get(0))) { valid = false; errors[element.get(0).name] = this.messages[className]; validator.invalid[element.get(0).name] = true; diff --git a/package.json.sample b/package.json.sample index 384aa88c65611a9e20892aade71488b31847ac5c..40169b3179052d98ad2c1a79baf48e376689f3c1 100644 --- a/package.json.sample +++ b/package.json.sample @@ -18,7 +18,7 @@ "grunt-contrib-connect": "^0.9.0", "grunt-contrib-cssmin": "^0.10.0", "grunt-contrib-imagemin": "^0.9.2", - "grunt-contrib-jasmine": "^0.8.1", + "grunt-contrib-jasmine": "^1.0.0", "grunt-contrib-less": "^0.12.0", "grunt-contrib-watch": "^0.6.1", "grunt-eslint": "17.3.1", diff --git a/setup/config/di.config.php b/setup/config/di.config.php index 804f18462065aa3af62cbba93d1fa5339b548f7e..b0dcb452ccd4030cf9736e6ab4a521199d1a899f 100644 --- a/setup/config/di.config.php +++ b/setup/config/di.config.php @@ -21,6 +21,7 @@ return [ \Magento\Setup\Controller\Environment::class, \Magento\Setup\Controller\DependencyCheck::class, \Magento\Setup\Controller\DatabaseCheck::class, + \Magento\Setup\Controller\UrlCheck::class, \Magento\Setup\Controller\ValidateAdminCredentials::class, \Magento\Setup\Controller\AddDatabase::class, \Magento\Setup\Controller\WebConfiguration::class, diff --git a/setup/performance-toolkit/profiles/ce/attributeSets.xml b/setup/performance-toolkit/config/attributeSets.xml similarity index 100% rename from setup/performance-toolkit/profiles/ce/attributeSets.xml rename to setup/performance-toolkit/config/attributeSets.xml diff --git a/setup/performance-toolkit/profiles/ce/searchConfig.xml b/setup/performance-toolkit/config/searchConfig.xml similarity index 100% rename from setup/performance-toolkit/profiles/ce/searchConfig.xml rename to setup/performance-toolkit/config/searchConfig.xml diff --git a/setup/performance-toolkit/profiles/ce/searchTerms.xml b/setup/performance-toolkit/config/searchTerms.xml similarity index 100% rename from setup/performance-toolkit/profiles/ce/searchTerms.xml rename to setup/performance-toolkit/config/searchTerms.xml diff --git a/setup/performance-toolkit/profiles/ce/extra_large.xml b/setup/performance-toolkit/profiles/ce/extra_large.xml index c9bf96ad4f4cab4cb8a6c1aacbe6e89a673e842c..fd1da116bc92b39fa23a3a4821daec93889fbad4 100644 --- a/setup/performance-toolkit/profiles/ce/extra_large.xml +++ b/setup/performance-toolkit/profiles/ce/extra_large.xml @@ -68,8 +68,8 @@ <set_scheduled>true</set_scheduled> </indexer> </indexers> - <xi:include href="searchTerms.xml" /> - <xi:include href="searchConfig.xml" /> - <xi:include href="attributeSets.xml" /> + <xi:include href="../../config/searchTerms.xml" /> + <xi:include href="../../config/searchConfig.xml" /> + <xi:include href="../../config/attributeSets.xml" /> </profile> </config> diff --git a/setup/performance-toolkit/profiles/ce/large.xml b/setup/performance-toolkit/profiles/ce/large.xml index 07e7ac33023b17d5d626986b76d84e88b3f7314b..d5d381fe059c95316d43ef88f56280c27494b197 100644 --- a/setup/performance-toolkit/profiles/ce/large.xml +++ b/setup/performance-toolkit/profiles/ce/large.xml @@ -68,8 +68,8 @@ <set_scheduled>true</set_scheduled> </indexer> </indexers> - <xi:include href="searchTerms.xml" /> - <xi:include href="searchConfig.xml" /> - <xi:include href="attributeSets.xml" /> + <xi:include href="../../config/searchTerms.xml" /> + <xi:include href="../../config/searchConfig.xml" /> + <xi:include href="../../config/attributeSets.xml" /> </profile> </config> diff --git a/setup/performance-toolkit/profiles/ce/medium.xml b/setup/performance-toolkit/profiles/ce/medium.xml index 6b1b289e823bab41a6d637350037b64c39e96935..95cd062fa74b6b94233066a6f0d0d3b296480b6b 100644 --- a/setup/performance-toolkit/profiles/ce/medium.xml +++ b/setup/performance-toolkit/profiles/ce/medium.xml @@ -68,8 +68,8 @@ <set_scheduled>false</set_scheduled> </indexer> </indexers> - <xi:include href="searchTerms.xml" /> - <xi:include href="searchConfig.xml" /> - <xi:include href="attributeSets.xml" /> + <xi:include href="../../config/searchTerms.xml" /> + <xi:include href="../../config/searchConfig.xml" /> + <xi:include href="../../config/attributeSets.xml" /> </profile> </config> diff --git a/setup/performance-toolkit/profiles/ce/small.xml b/setup/performance-toolkit/profiles/ce/small.xml index 4f9b436666f8c743c0fd0058c6fc747944506c9d..1949bb2a53f9aa69237b544ade511f4dd39815e0 100644 --- a/setup/performance-toolkit/profiles/ce/small.xml +++ b/setup/performance-toolkit/profiles/ce/small.xml @@ -68,8 +68,8 @@ <set_scheduled>false</set_scheduled> </indexer> </indexers> - <xi:include href="searchTerms.xml" /> - <xi:include href="searchConfig.xml" /> - <xi:include href="attributeSets.xml" /> + <xi:include href="../../config/searchTerms.xml" /> + <xi:include href="../../config/searchConfig.xml" /> + <xi:include href="../../config/attributeSets.xml" /> </profile> </config> diff --git a/setup/pub/magento/setup/web-configuration.js b/setup/pub/magento/setup/web-configuration.js index 03a0fc7845dda90225d3be867f4bf9ddd1621f63..47458056b33b52f8913017a5f6f7c6317fd98347 100644 --- a/setup/pub/magento/setup/web-configuration.js +++ b/setup/pub/magento/setup/web-configuration.js @@ -5,7 +5,7 @@ 'use strict'; angular.module('web-configuration', ['ngStorage']) - .controller('webConfigurationController', ['$scope', '$state', '$localStorage', function ($scope, $state, $localStorage) { + .controller('webConfigurationController', ['$scope', '$state', '$localStorage', '$http', function ($scope, $state, $localStorage, $http) { $scope.config = { address: { base_url: '', @@ -119,4 +119,28 @@ angular.module('web-configuration', ['ngStorage']) $scope.webconfig.submitted = false; } }); + + // Validate URL + $scope.validateUrl = function () { + if (!$scope.webconfig.submitted) { + $http.post('index.php/url-check', $scope.config) + .success(function (data) { + $scope.validateUrl.result = data; + if ($scope.validateUrl.result.successUrl && $scope.validateUrl.result.successSecureUrl) { + $scope.nextState(); + } + if (!$scope.validateUrl.result.successUrl) { + $scope.webconfig.submitted = true; + $scope.webconfig.base_url.$setValidity('url', false); + } + if (!$scope.validateUrl.result.successSecureUrl) { + $scope.webconfig.submitted = true; + $scope.webconfig.https.$setValidity('url', false); + } + }) + .error(function (data) { + $scope.validateUrl.failed = data; + }); + } + }; }]); diff --git a/setup/src/Magento/Setup/Console/Command/InstallStoreConfigurationCommand.php b/setup/src/Magento/Setup/Console/Command/InstallStoreConfigurationCommand.php index 928fe1e3d4b8d32f7bc4656bf6c00ac500972234..f1098af2db00916e328f515a2a88696553603f78 100644 --- a/setup/src/Magento/Setup/Console/Command/InstallStoreConfigurationCommand.php +++ b/setup/src/Magento/Setup/Console/Command/InstallStoreConfigurationCommand.php @@ -16,11 +16,10 @@ use Magento\Setup\Model\StoreConfigurationDataMapper; use Magento\Setup\Model\ObjectManagerProvider; use Magento\Framework\ObjectManagerInterface; use Magento\Framework\Exception\LocalizedException; -use Magento\Store\Model\Store; -use Magento\Framework\Validator\Locale; -use Magento\Framework\Validator\Timezone; -use Magento\Framework\Validator\Currency; -use Magento\Framework\Url\Validator; +use Magento\Framework\Validator\Locale as LocaleValidator; +use Magento\Framework\Validator\Timezone as TimezoneValidator; +use Magento\Framework\Validator\Currency as CurrencyValidator; +use Magento\Framework\Validator\Url as UrlValidator; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -43,24 +42,57 @@ class InstallStoreConfigurationCommand extends AbstractSetupCommand * Object Manager * * @var ObjectManagerInterface + * @deprecated */ private $objectManager; + /** + * @var LocaleValidator + */ + private $localeValidator; + + /** + * @var TimezoneValidator + */ + private $timezoneValidator; + + /** + * @var CurrencyValidator + */ + private $currencyValidator; + + /** + * @var UrlValidator + */ + private $urlValidator; + /** * Inject dependencies * * @param InstallerFactory $installerFactory * @param DeploymentConfig $deploymentConfig * @param ObjectManagerProvider $objectManagerProvider + * @param LocaleValidator $localeValidator, + * @param TimezoneValidator $timezoneValidator, + * @param CurrencyValidator $currencyValidator, + * @param UrlValidator $urlValidator */ public function __construct( InstallerFactory $installerFactory, DeploymentConfig $deploymentConfig, - ObjectManagerProvider $objectManagerProvider + ObjectManagerProvider $objectManagerProvider, + LocaleValidator $localeValidator, + TimezoneValidator $timezoneValidator, + CurrencyValidator $currencyValidator, + UrlValidator $urlValidator ) { $this->installerFactory = $installerFactory; $this->deploymentConfig = $deploymentConfig; $this->objectManager = $objectManagerProvider->get(); + $this->localeValidator = $localeValidator; + $this->timezoneValidator = $timezoneValidator; + $this->currencyValidator = $currencyValidator; + $this->urlValidator = $urlValidator; parent::__construct(); } @@ -173,6 +205,7 @@ class InstallStoreConfigurationCommand extends AbstractSetupCommand public function validate(InputInterface $input) { $errors = []; + $errorMsg = ''; $options = $input->getOptions(); foreach ($options as $key => $value) { if (!$value) { @@ -180,99 +213,69 @@ class InstallStoreConfigurationCommand extends AbstractSetupCommand } switch ($key) { case StoreConfigurationDataMapper::KEY_BASE_URL: - /** @var Validator $url */ if (strcmp($value, '{{base_url}}') == 0) { break; } - $url = $this->objectManager->get(\Magento\Framework\Url\Validator::class); - if (!$url->isValid($value)) { - $errorMsgs = $url->getMessages(); - $errors[] = '<error>' . 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL - . '\': ' . $errorMsgs[Validator::INVALID_URL] .'</error>'; - } + $errorMsg = $this->validateUrl( + $value, + StoreConfigurationDataMapper::KEY_BASE_URL, + ['http', 'https'] + ); + break; case StoreConfigurationDataMapper::KEY_LANGUAGE: - /** @var Locale $lists */ - $lists = $this->objectManager->get(\Magento\Framework\Validator\Locale::class); - $errorMsg = $this->validateCodes($lists, $value, StoreConfigurationDataMapper::KEY_LANGUAGE); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } + $errorMsg = $this->validateCodes( + $this->localeValidator, + $value, + StoreConfigurationDataMapper::KEY_LANGUAGE + ); break; case StoreConfigurationDataMapper::KEY_TIMEZONE: - /** @var Timezone $lists */ - $lists = $this->objectManager->get(\Magento\Framework\Validator\Timezone::class); - $errorMsg = $this->validateCodes($lists, $value, StoreConfigurationDataMapper::KEY_TIMEZONE); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } + $errorMsg = $this->validateCodes( + $this->timezoneValidator, + $value, + StoreConfigurationDataMapper::KEY_TIMEZONE + ); break; case StoreConfigurationDataMapper::KEY_CURRENCY: - /** @var Currency $lists */ - $lists = $this->objectManager->get(\Magento\Framework\Validator\Currency::class); - $errorMsg = $this->validateCodes($lists, $value, StoreConfigurationDataMapper::KEY_CURRENCY); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } + $errorMsg = $this->validateCodes( + $this->currencyValidator, + $value, + StoreConfigurationDataMapper::KEY_CURRENCY + ); break; case StoreConfigurationDataMapper::KEY_USE_SEF_URL: $errorMsg = $this->validateBinaryValue($value, StoreConfigurationDataMapper::KEY_USE_SEF_URL); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; case StoreConfigurationDataMapper::KEY_IS_SECURE: $errorMsg = $this->validateBinaryValue($value, StoreConfigurationDataMapper::KEY_IS_SECURE); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; case StoreConfigurationDataMapper::KEY_BASE_URL_SECURE: - try { - /** @var Validator $url */ - $url = $this->objectManager->get(\Magento\Framework\Url\Validator::class); - $errorMsgs = ''; - if (!$url->isValid($value)) { - $errorMsgs = $url->getMessages(); - if (!empty($errorMsgs)) { - $errors[] = '<error>' . 'Command option \'' - . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE - . '\': ' . $errorMsgs[Validator::INVALID_URL] .'</error>'; - } - } - if (empty($errorMsgs) && strpos($value, 'https:') === false) { - throw new LocalizedException(new \Magento\Framework\Phrase("Invalid secure URL.")); - } - } catch (LocalizedException $e) { - $errors[] = '<error>' . 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE - . '\': ' . $e->getLogMessage() .'</error>'; - } + $errorMsg = $this->validateUrl( + $value, + StoreConfigurationDataMapper::KEY_BASE_URL_SECURE, + ['https'] + ); break; case StoreConfigurationDataMapper::KEY_IS_SECURE_ADMIN: $errorMsg = $this->validateBinaryValue($value, StoreConfigurationDataMapper::KEY_IS_SECURE_ADMIN); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; case StoreConfigurationDataMapper::KEY_ADMIN_USE_SECURITY_KEY: $errorMsg = $this->validateBinaryValue( $value, StoreConfigurationDataMapper::KEY_ADMIN_USE_SECURITY_KEY ); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; case StoreConfigurationDataMapper::KEY_JS_LOGGING: $errorMsg = $this->validateBinaryValue( $value, StoreConfigurationDataMapper::KEY_JS_LOGGING ); - if ($errorMsg !== '') { - $errors[] = $errorMsg; - } break; } + if ($errorMsg !== '') { + $errors[] = $errorMsg; + } } return $errors; } @@ -296,7 +299,7 @@ class InstallStoreConfigurationCommand extends AbstractSetupCommand /** * Validate codes for languages, currencies or timezones * - * @param Locale|Timezone|Currency $lists + * @param LocaleValidator|TimezoneValidator|CurrencyValidator $lists * @param string $code * @param string $type * @return string @@ -310,4 +313,31 @@ class InstallStoreConfigurationCommand extends AbstractSetupCommand } return $errorMsg; } + + /** + * Validate URL + * + * @param string $url + * @param string $option + * @param array $allowedSchemes + * @return string + */ + private function validateUrl($url, $option, array $allowedSchemes) + { + $errorMsg = ''; + + if (!$this->urlValidator->isValid($url, $allowedSchemes)) { + $errorTemplate = '<error>Command option \'%s\': Invalid URL \'%s\'.' + . ' Domain Name should contain only letters, digits and hyphen.' + . ' And you should use only following schemes: \'%s\'.</error>'; + $errorMsg = sprintf( + $errorTemplate, + $option, + $url, + implode(', ', $allowedSchemes) + ); + } + + return $errorMsg; + } } diff --git a/setup/src/Magento/Setup/Controller/UrlCheck.php b/setup/src/Magento/Setup/Controller/UrlCheck.php new file mode 100644 index 0000000000000000000000000000000000000000..08a0d50404c0a2e457a2646a30860d1f958f14d3 --- /dev/null +++ b/setup/src/Magento/Setup/Controller/UrlCheck.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Setup\Controller; + +use Zend\Mvc\Controller\AbstractActionController; +use Zend\View\Model\JsonModel; +use Zend\Json\Json; +use Magento\Framework\Validator\Url as UrlValidator; + +class UrlCheck extends AbstractActionController +{ + /** + * @var UrlValidator + */ + private $urlValidator; + + /** + * @param UrlValidator $urlValidator + */ + public function __construct(UrlValidator $urlValidator) + { + $this->urlValidator = $urlValidator; + } + + /** + * Validate URL + * + * @return JsonModel + */ + public function indexAction() + { + $params = Json::decode($this->getRequest()->getContent(), Json::TYPE_ARRAY); + $result = ['successUrl' => false, 'successSecureUrl' => true]; + + $hasBaseUrl = isset($params['address']['actual_base_url']); + $hasSecureBaseUrl = isset($params['https']['text']); + $hasSecureAdminUrl = !empty($params['https']['admin']); + $hasSecureFrontUrl = !empty($params['https']['front']); + $schemes = ['http', 'https']; + + // Validating of Base URL + if ($hasBaseUrl && $this->urlValidator->isValid($params['address']['actual_base_url'], $schemes)) { + $result['successUrl'] = true; + } + + // Validating of Secure Base URL + if ($hasSecureAdminUrl || $hasSecureFrontUrl) { + if (!($hasSecureBaseUrl && $this->urlValidator->isValid($params['https']['text'], $schemes))) { + $result['successSecureUrl'] = false; + } + } + + return new JsonModel($result); + } +} diff --git a/setup/src/Magento/Setup/Fixtures/FixtureModel.php b/setup/src/Magento/Setup/Fixtures/FixtureModel.php index 44173a32c3e771ec2c43480a73c380ad2a2691ab..997244f5508933d05ae731255b81779f2528a101 100644 --- a/setup/src/Magento/Setup/Fixtures/FixtureModel.php +++ b/setup/src/Magento/Setup/Fixtures/FixtureModel.php @@ -221,6 +221,12 @@ class FixtureModel */ public function getValue($key, $default = null) { - return isset($this->config['config']['profile'][$key]) ? $this->config['config']['profile'][$key] : $default; + return isset($this->config['config']['profile'][$key]) ? + ( + // Work around for how attributes are handled in the XML parser when injected via xinclude due to the + // files existing outside of the current working directory. + isset($this->config['config']['profile'][$key]['_value']) ? + $this->config['config']['profile'][$key]['_value'] : $this->config['config']['profile'][$key] + ) : $default; } } diff --git a/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php b/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php index 6a4b3a72b372c8ff611bcc7e535c99cbef4bb404..935f395a5e219f0546daf7967dbcc0726c695055 100644 --- a/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php +++ b/setup/src/Magento/Setup/Mvc/Bootstrap/InitParamListener.php @@ -99,10 +99,10 @@ class InitParamListener implements ListenerAggregateInterface, FactoryInterface } /** - * Check if user login + * Check if user logged-in and has permissions * * @param \Zend\Mvc\MvcEvent $event - * @return bool + * @return false|\Zend\Http\Response * @throws \Magento\Framework\Exception\LocalizedException */ public function authPreDispatch($event) @@ -115,6 +115,7 @@ class InitParamListener implements ListenerAggregateInterface, FactoryInterface /** @var Application $application */ $application = $event->getApplication(); $serviceManager = $application->getServiceManager(); + if ($serviceManager->get(\Magento\Framework\App\DeploymentConfig::class)->isAvailable()) { /** @var \Magento\Setup\Model\ObjectManagerProvider $objectManagerProvider */ $objectManagerProvider = $serviceManager->get(\Magento\Setup\Model\ObjectManagerProvider::class); @@ -135,17 +136,26 @@ class InitParamListener implements ListenerAggregateInterface, FactoryInterface 'appState' => $adminAppState ] ); - if (!$objectManager->get(\Magento\Backend\Model\Auth::class)->isLoggedIn()) { + /** @var \Magento\Backend\Model\Auth $auth */ + $authentication = $objectManager->get(\Magento\Backend\Model\Auth::class); + + if ( + !$authentication->isLoggedIn() || + !$adminSession->isAllowed('Magento_Backend::setup_wizard') + ) { $adminSession->destroy(); + /** @var \Zend\Http\Response $response */ $response = $event->getResponse(); $baseUrl = Http::getDistroBaseUrlPath($_SERVER); $response->getHeaders()->addHeaderLine('Location', $baseUrl . 'index.php/session/unlogin'); $response->setStatusCode(302); $event->stopPropagation(); + return $response; } } } + return false; } diff --git a/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallStoreConfigurationCommandTest.php b/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallStoreConfigurationCommandTest.php index 6f5ab3a5a64c537751cc8964229e35869b06c8e5..c3494b389aa5ecb2a2ab2465b5d848627a5d8e7a 100644 --- a/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallStoreConfigurationCommandTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Console/Command/InstallStoreConfigurationCommandTest.php @@ -11,8 +11,14 @@ use Symfony\Component\Console\Tester\CommandTester; use Magento\Setup\Model\Installer; use Magento\Framework\ObjectManagerInterface; use Magento\Setup\Model\StoreConfigurationDataMapper; -use Magento\Framework\Url\Validator; +use Magento\Framework\Validator\Url as UrlValidator; +use Magento\Framework\Validator\Locale as LocaleValidator; +use Magento\Framework\Validator\Timezone as TimezoneValidator; +use Magento\Framework\Validator\Currency as CurrencyValidator; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class InstallStoreConfigurationCommandTest extends \PHPUnit_Framework_TestCase { /** @@ -35,6 +41,26 @@ class InstallStoreConfigurationCommandTest extends \PHPUnit_Framework_TestCase */ private $objectManager; + /** + * @var LocaleValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $localeValidatorMock; + + /** + * @var TimezoneValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $timezoneValidatorMock; + + /** + * @var CurrencyValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $currencyValidatorMock; + + /** + * @var UrlValidator|\PHPUnit_Framework_MockObject_MockObject + */ + private $urlValidatorMock; + /** * @var InstallStoreConfigurationCommand */ @@ -42,6 +68,11 @@ class InstallStoreConfigurationCommandTest extends \PHPUnit_Framework_TestCase protected function setUp() { + $this->urlValidatorMock = $this->getMock(UrlValidator::class, [], [], '', false); + $this->localeValidatorMock = $this->getMock(LocaleValidator::class, [], [], '', false); + $this->timezoneValidatorMock = $this->getMock(TimezoneValidator::class, [], [], '', false); + $this->currencyValidatorMock = $this->getMock(CurrencyValidator::class, [], [], '', false); + $this->installerFactory = $this->getMock(\Magento\Setup\Model\InstallerFactory::class, [], [], '', false); $this->deploymentConfig = $this->getMock(\Magento\Framework\App\DeploymentConfig::class, [], [], '', false); $this->installer = $this->getMock(\Magento\Setup\Model\Installer::class, [], [], '', false); @@ -62,7 +93,11 @@ class InstallStoreConfigurationCommandTest extends \PHPUnit_Framework_TestCase $this->command = new InstallStoreConfigurationCommand( $this->installerFactory, $this->deploymentConfig, - $objectManagerProvider + $objectManagerProvider, + $this->localeValidatorMock, + $this->timezoneValidatorMock, + $this->currencyValidatorMock, + $this->urlValidatorMock ); } @@ -102,41 +137,11 @@ class InstallStoreConfigurationCommandTest extends \PHPUnit_Framework_TestCase */ public function testExecuteInvalidData(array $option, $error) { - $url= $this->getMock(\Magento\Framework\Url\Validator::class, [], [], '', false); - $url->expects($this->any())->method('isValid')->will($this->returnValue(false)); - if (!isset($option['--' . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE])) { - $url->expects($this->any())->method('getMessages')->will($this->returnValue([ - Validator::INVALID_URL => 'Invalid URL.' - ])); - } - $localeLists= $this->getMock(\Magento\Framework\Validator\Locale::class, [], [], '', false); - $localeLists->expects($this->any())->method('isValid')->will($this->returnValue(false)); - $timezoneLists= $this->getMock(\Magento\Framework\Validator\Timezone::class, [], [], '', false); - $timezoneLists->expects($this->any())->method('isValid')->will($this->returnValue(false)); - $currencyLists= $this->getMock(\Magento\Framework\Validator\Currency::class, [], [], '', false); - $currencyLists->expects($this->any())->method('isValid')->will($this->returnValue(false)); - - $returnValueMapOM = [ - [ - \Magento\Framework\Url\Validator::class, - $url - ], - [ - \Magento\Framework\Validator\Locale::class, - $localeLists - ], - [ - \Magento\Framework\Validator\Timezone::class, - $timezoneLists - ], - [ - \Magento\Framework\Validator\Currency::class, - $currencyLists - ], - ]; - $this->objectManager->expects($this->any()) - ->method('get') - ->will($this->returnValueMap($returnValueMapOM)); + $this->localeValidatorMock->expects($this->any())->method('isValid')->willReturn(false); + $this->timezoneValidatorMock->expects($this->any())->method('isValid')->willReturn(false); + $this->currencyValidatorMock->expects($this->any())->method('isValid')->willReturn(false); + $this->urlValidatorMock->expects($this->any())->method('isValid')->willReturn(false); + $this->deploymentConfig->expects($this->once()) ->method('isAvailable') ->will($this->returnValue(true)); @@ -144,7 +149,7 @@ class InstallStoreConfigurationCommandTest extends \PHPUnit_Framework_TestCase ->method('create'); $commandTester = new CommandTester($this->command); $commandTester->execute($option); - $this->assertEquals($error . PHP_EOL, $commandTester->getDisplay()); + $this->assertContains($error, $commandTester->getDisplay()); } /** @@ -155,48 +160,54 @@ class InstallStoreConfigurationCommandTest extends \PHPUnit_Framework_TestCase return [ [ ['--' . StoreConfigurationDataMapper::KEY_BASE_URL => 'sampleUrl'], - 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL . '\': Invalid URL.' + 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL . '\': Invalid URL \'sampleUrl\'.' + ], + [ + ['--' . StoreConfigurationDataMapper::KEY_BASE_URL => 'http://example.com_test'], + 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL + . '\': Invalid URL \'http://example.com_test\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_LANGUAGE => 'sampleLanguage'], 'Command option \'' . StoreConfigurationDataMapper::KEY_LANGUAGE - . '\': Invalid value. To see possible values, run command \'bin/magento info:language:list\'.' + . '\': Invalid value. To see possible values, run command \'bin/magento info:language:list\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_TIMEZONE => 'sampleTimezone'], 'Command option \'' . StoreConfigurationDataMapper::KEY_TIMEZONE - . '\': Invalid value. To see possible values, run command \'bin/magento info:timezone:list\'.' + . '\': Invalid value. To see possible values, run command \'bin/magento info:timezone:list\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_CURRENCY => 'sampleLanguage'], 'Command option \'' . StoreConfigurationDataMapper::KEY_CURRENCY - . '\': Invalid value. To see possible values, run command \'bin/magento info:currency:list\'.' + . '\': Invalid value. To see possible values, run command \'bin/magento info:currency:list\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_USE_SEF_URL => 'invalidValue'], 'Command option \'' . StoreConfigurationDataMapper::KEY_USE_SEF_URL - . '\': Invalid value. Possible values (0|1).' + . '\': Invalid value. Possible values (0|1).' ], [ ['--' . StoreConfigurationDataMapper::KEY_IS_SECURE => 'invalidValue'], 'Command option \'' . StoreConfigurationDataMapper::KEY_IS_SECURE - . '\': Invalid value. Possible values (0|1).' + . '\': Invalid value. Possible values (0|1).' ], [ ['--' . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE => 'http://www.sample.com'], 'Command option \'' . StoreConfigurationDataMapper::KEY_BASE_URL_SECURE - . '\': Invalid secure URL.' + . '\': Invalid URL \'http://www.sample.com\'.' ], [ ['--' . StoreConfigurationDataMapper::KEY_IS_SECURE_ADMIN => 'invalidValue'], 'Command option \'' . StoreConfigurationDataMapper::KEY_IS_SECURE_ADMIN - . '\': Invalid value. Possible values (0|1).' + . '\': Invalid value. Possible values (0|1).' ], [ ['--' . StoreConfigurationDataMapper::KEY_ADMIN_USE_SECURITY_KEY => 'invalidValue'], 'Command option \'' . StoreConfigurationDataMapper::KEY_ADMIN_USE_SECURITY_KEY - . '\': Invalid value. Possible values (0|1).' + . '\': Invalid value. Possible values (0|1).' ], + ]; } } diff --git a/setup/src/Magento/Setup/Test/Unit/Controller/UrlCheckTest.php b/setup/src/Magento/Setup/Test/Unit/Controller/UrlCheckTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0b44887b586d5c69b6c56a1e19a4b9d68ae96bfe --- /dev/null +++ b/setup/src/Magento/Setup/Test/Unit/Controller/UrlCheckTest.php @@ -0,0 +1,143 @@ +<?php +/** + * Copyright © 2016 Magento. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\Setup\Test\Unit\Controller; + +use Magento\Setup\Controller\UrlCheck; +use Zend\Stdlib\RequestInterface; +use Zend\View\Model\JsonModel; +use Magento\Framework\Validator\Url as UrlValidator; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; + +class UrlCheckTest extends \PHPUnit_Framework_TestCase +{ + /** + * @param array $requestJson + * @param array $expectedResult + * @dataProvider indexActionDataProvider + */ + public function testIndexAction($requestJson, $expectedResult) + { + /** @var ObjectManagerHelper $objectManagerHelper */ + $objectManagerHelper = new ObjectManagerHelper($this); + + $allowedSchemes = ['http', 'https']; + $returnMap = []; + if (isset($requestJson['address']['actual_base_url'])) { + $returnMap[] = [ + $requestJson['address']['actual_base_url'], + $allowedSchemes, + $expectedResult['successUrl'], + ]; + } + if (isset($requestJson['https']['text'])) { + $returnMap[] = [ + $requestJson['https']['text'], + $allowedSchemes, + $expectedResult['successSecureUrl'], + ]; + } + + /** @var UrlValidator|\PHPUnit_Framework_MockObject_MockObject $validator */ + $validator = $this->getMockBuilder(UrlValidator::class) + ->disableOriginalConstructor() + ->getMock(); + $validator->expects($this->any()) + ->method('isValid') + ->willReturnMap($returnMap); + + /** @var RequestInterface|\PHPUnit_Framework_MockObject_MockObject $requestMock */ + $requestMock = $this->getMockBuilder(RequestInterface::class) + ->getMockForAbstractClass(); + $requestMock->expects($this->once()) + ->method('getContent') + ->willReturn(json_encode($requestJson)); + + $controller = $objectManagerHelper->getObject( + UrlCheck::class, + ['urlValidator' => $validator] + ); + $objectManagerHelper->setBackwardCompatibleProperty($controller, 'request', $requestMock); + + $this->assertEquals(new JsonModel($expectedResult), $controller->indexAction()); + } + + /** + * @return array + */ + public function indexActionDataProvider() + { + return [ + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost' + ] + ], + 'expectedResult' => ['successUrl' => true, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com_test' + ] + ], + 'expectedResult' => ['successUrl' => false, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com_test' + ], + 'https' => [ + 'admin' => false, + 'front' => false, + 'text' => '' + ] + ], + 'expectedResult' => ['successUrl' => false, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com:8080' + ], + 'https' => [ + 'admin' => true, + 'front' => false, + 'text' => 'https://example.com.ua/' + ] + ], + 'expectedResult' => ['successUrl' => true, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com:8080/folder_name/' + ], + 'https' => [ + 'admin' => false, + 'front' => true, + 'text' => 'https://example.com.ua/' + ] + ], + 'expectedResult' => ['successUrl' => true, 'successSecureUrl' => true] + ], + [ + 'requestJson' => [ + 'address' => [ + 'actual_base_url' => 'http://localhost.com:8080/folder_name/' + ], + 'https' => [ + 'admin' => true, + 'front' => true, + 'text' => 'https://example.com.ua:8090/folder_name/' + ] + ], + 'expectedResult' => ['successUrl' => true, 'successSecureUrl' => true] + ], + ]; + } +} diff --git a/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php b/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php index 558533afe92429582cbfa79e5fcd691b200ecef8..1ace3f452c4f917181c43a86744f2b4db39b9439 100644 --- a/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php +++ b/setup/src/Magento/Setup/Test/Unit/Mvc/Bootstrap/InitParamListenerTest.php @@ -3,7 +3,6 @@ * Copyright © 2016 Magento. All rights reserved. * See COPYING.txt for license details. */ - namespace Magento\Setup\Test\Unit\Mvc\Bootstrap; use \Magento\Setup\Mvc\Bootstrap\InitParamListener; @@ -59,11 +58,11 @@ class InitParamListenerTest extends \PHPUnit_Framework_TestCase ->withConsecutive( [ \Magento\Framework\App\Filesystem\DirectoryList::class, - $this->isInstanceOf(\Magento\Framework\App\Filesystem\DirectoryList::class) + $this->isInstanceOf(\Magento\Framework\App\Filesystem\DirectoryList::class), ], [ \Magento\Framework\Filesystem::class, - $this->isInstanceOf(\Magento\Framework\Filesystem::class) + $this->isInstanceOf(\Magento\Framework\Filesystem::class), ] ); $mvcApplication->expects($this->any())->method('getServiceManager')->willReturn($serviceManager); @@ -130,10 +129,10 @@ class InitParamListenerTest extends \PHPUnit_Framework_TestCase $request->expects($this->any()) ->method('getContent') ->willReturn( - $cliParam ? ['install', '--magento-init-params=' . $cliParam ] : ['install'] + $cliParam ? ['install', '--magento-init-params=' . $cliParam] : ['install'] ); $mvcApplication->expects($this->any())->method('getConfig')->willReturn( - $zfAppConfig ? [InitParamListener::BOOTSTRAP_PARAM => $zfAppConfig]:[] + $zfAppConfig ? [InitParamListener::BOOTSTRAP_PARAM => $zfAppConfig] : [] ); $mvcApplication->expects($this->any())->method('getRequest')->willReturn($request); @@ -150,41 +149,55 @@ class InitParamListenerTest extends \PHPUnit_Framework_TestCase 'mage_mode App' => [['MAGE_MODE' => 'developer'], [], '', ['MAGE_MODE' => 'developer']], 'mage_mode Env' => [[], ['MAGE_MODE' => 'developer'], '', ['MAGE_MODE' => 'developer']], 'mage_mode CLI' => [[], [], 'MAGE_MODE=developer', ['MAGE_MODE' => 'developer']], - 'one MAGE_DIRS CLI' => [[], [], 'MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/magento2', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer']], + 'one MAGE_DIRS CLI' => [ + [], + [], + 'MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/magento2', + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer'], + ], 'two MAGE_DIRS CLI' => [ [], [], 'MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/magento2&MAGE_DIRS[cache][path]=/tmp/cache', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2'], 'cache' => ['path' => '/tmp/cache']], - 'MAGE_MODE' => 'developer']], + [ + 'MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2'], 'cache' => ['path' => '/tmp/cache']], + 'MAGE_MODE' => 'developer', + ], + ], 'mage_mode only' => [[], [], 'MAGE_MODE=developer', ['MAGE_MODE' => 'developer']], 'MAGE_DIRS Env' => [ [], ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer'], '', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer']], + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2']], 'MAGE_MODE' => 'developer'], + ], 'two MAGE_DIRS' => [ [], [], 'MAGE_MODE=developer&MAGE_DIRS[base][path]=/var/www/magento2&MAGE_DIRS[cache][path]=/tmp/cache', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2'], 'cache' => ['path' => '/tmp/cache']], - 'MAGE_MODE' => 'developer']], + [ + 'MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2'], 'cache' => ['path' => '/tmp/cache']], + 'MAGE_MODE' => 'developer', + ], + ], 'Env overwrites App' => [ ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/App']], 'MAGE_MODE' => 'developer'], ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']], 'MAGE_MODE' => 'developer'], '', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']], 'MAGE_MODE' => 'developer']], + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']], 'MAGE_MODE' => 'developer'], + ], 'CLI overwrites Env' => [ ['MAGE_MODE' => 'developerApp'], ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']]], 'MAGE_DIRS[base][path]=/var/www/magento2/CLI', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/CLI']], 'MAGE_MODE' => 'developerApp']], + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/CLI']], 'MAGE_MODE' => 'developerApp'], + ], 'CLI overwrites All' => [ ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/App']], 'MAGE_MODE' => 'production'], ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/Env']]], 'MAGE_DIRS[base][path]=/var/www/magento2/CLI', - ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/CLI']], 'MAGE_MODE' => 'production']], + ['MAGE_DIRS' => ['base' => ['path' => '/var/www/magento2/CLI']], 'MAGE_MODE' => 'production'], + ], ]; } @@ -226,6 +239,168 @@ class InitParamListenerTest extends \PHPUnit_Framework_TestCase [$this->listener, 'onBootstrap'] )->willReturn($this->callbackHandler); $eventManager->expects($this->once())->method('getSharedManager')->willReturn($sharedManager); + return $eventManager; } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function testAuthPreDispatch() + { + $eventMock = $this->getMockBuilder(\Zend\Mvc\MvcEvent::class) + ->disableOriginalConstructor() + ->getMock(); + $routeMatchMock = $this->getMockBuilder(\Zend\Mvc\Router\Http\RouteMatch::class) + ->disableOriginalConstructor() + ->getMock(); + $applicationMock = $this->getMockBuilder(\Zend\Mvc\Application::class) + ->disableOriginalConstructor() + ->getMock(); + $serviceManagerMock = $this->getMockBuilder(\Zend\ServiceManager\ServiceManager::class) + ->disableOriginalConstructor() + ->getMock(); + $deploymentConfigMock = $this->getMockBuilder(\Magento\Framework\App\DeploymentConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $deploymentConfigMock->expects($this->once()) + ->method('isAvailable') + ->willReturn(true); + $omProvider = $this->getMockBuilder(\Magento\Setup\Model\ObjectManagerProvider::class) + ->disableOriginalConstructor() + ->getMock(); + $objectManagerMock = $this->getMockForAbstractClass(\Magento\Framework\ObjectManagerInterface::class); + $adminAppStateMock = $this->getMockBuilder(\Magento\Framework\App\State::class) + ->disableOriginalConstructor() + ->getMock(); + $sessionConfigMock = $this->getMockBuilder(\Magento\Backend\Model\Session\AdminConfig::class) + ->disableOriginalConstructor() + ->getMock(); + $backendAppListMock = $this->getMockBuilder(\Magento\Backend\App\BackendAppList::class) + ->disableOriginalConstructor() + ->getMock(); + $backendAppMock = $this->getMockBuilder(\Magento\Backend\App\BackendApp::class) + ->disableOriginalConstructor() + ->getMock(); + $backendUrlFactoryMock = $this->getMockBuilder(\Magento\Backend\Model\UrlFactory::class) + ->setMethods(['create']) + ->disableOriginalConstructor() + ->getMock(); + $backendUrlMock = $this->getMockBuilder(\Magento\Backend\Model\Url::class) + ->disableOriginalConstructor() + ->getMock(); + $authenticationMock = $this->getMockBuilder(\Magento\Backend\Model\Auth::class) + ->disableOriginalConstructor() + ->getMock(); + $adminSessionMock = $this->getMockBuilder(\Magento\Backend\Model\Auth\Session::class) + ->disableOriginalConstructor() + ->getMock(); + $responseMock = $this->getMockBuilder(\Zend\Http\Response::class) + ->disableOriginalConstructor() + ->getMock(); + $headersMock = $this->getMockBuilder(\Zend\Http\Headers::class) + ->disableOriginalConstructor() + ->getMock(); + + $routeMatchMock->expects($this->once()) + ->method('getParam') + ->with('controller') + ->willReturn('testController'); + $eventMock->expects($this->once()) + ->method('getRouteMatch') + ->willReturn($routeMatchMock); + $eventMock->expects($this->once()) + ->method('getApplication') + ->willReturn($applicationMock); + $serviceManagerMock->expects($this->any()) + ->method('get') + ->willReturnMap( + [ + [ + \Magento\Framework\App\DeploymentConfig::class, + true, + $deploymentConfigMock, + ], + [ + \Magento\Setup\Model\ObjectManagerProvider::class, + true, + $omProvider, + ], + ] + ); + $objectManagerMock->expects($this->any()) + ->method('get') + ->willReturnMap( + [ + [ + \Magento\Framework\App\State::class, + $adminAppStateMock, + ], + [ + \Magento\Backend\Model\Session\AdminConfig::class, + $sessionConfigMock, + ], + [ + \Magento\Backend\App\BackendAppList::class, + $backendAppListMock, + ], + [ + \Magento\Backend\Model\UrlFactory::class, + $backendUrlFactoryMock, + ], + [ + \Magento\Backend\Model\Auth::class, + $authenticationMock, + ], + ] + ); + $objectManagerMock->expects($this->any()) + ->method('create') + ->willReturn($adminSessionMock); + $omProvider->expects($this->once()) + ->method('get') + ->willReturn($objectManagerMock); + $adminAppStateMock->expects($this->once()) + ->method('setAreaCode') + ->with(\Magento\Framework\App\Area::AREA_ADMINHTML); + $applicationMock->expects($this->once()) + ->method('getServiceManager') + ->willReturn($serviceManagerMock); + $backendAppMock->expects($this->once()) + ->method('getCookiePath') + ->willReturn(''); + $backendUrlFactoryMock->expects($this->once()) + ->method('create') + ->willReturn($backendUrlMock); + $backendAppListMock->expects($this->once()) + ->method('getBackendApp') + ->willReturn($backendAppMock); + $authenticationMock->expects($this->once()) + ->method('isLoggedIn') + ->willReturn(true); + $adminSessionMock->expects($this->once()) + ->method('isAllowed') + ->with('Magento_Backend::setup_wizard', null) + ->willReturn(false); + $adminSessionMock->expects($this->once()) + ->method('destroy'); + $eventMock->expects($this->once()) + ->method('getResponse') + ->willReturn($responseMock); + $responseMock->expects($this->once()) + ->method('getHeaders') + ->willReturn($headersMock); + $headersMock->expects($this->once()) + ->method('addHeaderLine'); + $responseMock->expects($this->once()) + ->method('setStatusCode') + ->with(302); + $eventMock->expects($this->once()) + ->method('stopPropagation'); + + $this->assertSame( + $this->listener->authPreDispatch($eventMock), + $responseMock + ); + } } diff --git a/setup/view/magento/setup/web-configuration.phtml b/setup/view/magento/setup/web-configuration.phtml index 35aa19484ed53f39bf37ae0e90e7b76c7f5efe87..ab2a00342b715b05f215d1d3e12010b0fc4993cc 100644 --- a/setup/view/magento/setup/web-configuration.phtml +++ b/setup/view/magento/setup/web-configuration.phtml @@ -30,7 +30,7 @@ $hints = [ <div class="nav-bar-outer-actions"> <div class="outer-actions-inner-wrap"> <div class="btn-wrap btn-wrap-triangle-right btn-wrap-next"> - <button type="button" class="btn btn-prime" ng-click="nextState()" dis>Next</button> + <button type="button" class="btn btn-prime" ng-click="validateUrl()" dis>Next</button> </div> <div class="btn-wrap btn-wrap-triangle-left btn-wrap-prev"> <button type="button" class="btn" ng-click="previousState()">Back</button> @@ -40,6 +40,13 @@ $hints = [ <h2 class="page-sub-title">{{$state.current.header}}</h2> +<div + class="message message-error" + ng-show="validateUrl.failed !== undefined" +> + <span class="message-text">{{validateUrl.failed}}</span> +</div> + <form name="webconfig" role="form"