diff --git a/.gitignore b/.gitignore
index 9269cefc51fa0..a5b85031db205 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 ec60a1708dacc..c8e676cb69cff 100644
--- a/app/bootstrap.php
+++ b/app/bootstrap.php
@@ -11,14 +11,14 @@
#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 <<
-
Magento supports PHP 5.6, 7.0.2, 7.0.4, and 7.0.6 or later. Please read
+
Magento supports PHP 5.6.5, 7.0.2, 7.0.4, and 7.0.6 or later. Please read
Magento System Requirements.
@@ -35,6 +35,17 @@
$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 527268df36b41..09d8ce41bbb03 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 65ea7524dffff..2fc465fa3c3df 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 0ca367d4854df..af88e8376dc75 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 0c9e9641b6076..b93cb6688f56d 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 7183c706dc0a2..45025304c4e44 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 cd05960c17633..86ec8e0c39221 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 7d428636a1f45..5cfe6955b46bb 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 21ed6f1780a41..ee05d6726db4c 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 0000000000000..fd94e18e2cd94
--- /dev/null
+++ b/app/code/Magento/Braintree/Model/Ui/Adminhtml/PayPal/TokenUiComponentProvider.php
@@ -0,0 +1,84 @@
+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 6cfc96ea23d0d..420b8365b3ea4 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 @@ public function getComponentForToken(PaymentTokenInterface $paymentToken)
$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 0000000000000..bdc39cbc5b868
--- /dev/null
+++ b/app/code/Magento/Braintree/Test/Unit/Model/Ui/Adminhtml/PayPal/TokenUiComponentProviderTest.php
@@ -0,0 +1,114 @@
+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 d1665c71804cf..f159136cf4c46 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 @@
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 @@ public function testGetComponentForToken()
$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 3b77e208837a4..91cacf6add2b8 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 f252b977f20bd..d154aabbb01b5 100644
--- a/app/code/Magento/Braintree/etc/adminhtml/di.xml
+++ b/app/code/Magento/Braintree/etc/adminhtml/di.xml
@@ -47,6 +47,7 @@
- Magento\Braintree\Model\Ui\Adminhtml\TokenUiComponentProvider
+ - Magento\Braintree\Model\Ui\Adminhtml\PayPal\TokenUiComponentProvider
diff --git a/app/code/Magento/Braintree/etc/config.xml b/app/code/Magento/Braintree/etc/config.xml
index 095a8419c8529..bf19324ae7a02 100644
--- a/app/code/Magento/Braintree/etc/config.xml
+++ b/app/code/Magento/Braintree/etc/config.xml
@@ -71,7 +71,8 @@
BraintreePayPalVaultFacade
- Vault Token (Braintree PayPal)
+ Stored Accounts (Braintree PayPal)
+ 1
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 571c5ededeb99..5e4f36e1c1fb4 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 @@
braintree_cc_vault
Magento_Vault::form/vault.phtml
+
+ braintree_paypal_vault
+ Magento_Vault::form/vault.phtml
+
braintree_cc_vault
Magento_Vault::form/vault.phtml
+
+ braintree_paypal_vault
+ Magento_Vault::form/vault.phtml
+
\ 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 0000000000000..22930bbc65666
--- /dev/null
+++ b/app/code/Magento/Braintree/view/adminhtml/templates/form/paypal/vault.phtml
@@ -0,0 +1,30 @@
+getData(TokenUiComponentProviderInterface::COMPONENT_DETAILS);
+$icon = $details['icon'];
+$id = $block->escapeHtml($block->getData('id'));
+?>
+
",
+ "nonceUrl": "escapeUrl($block->getData('nonceUrl')); ?>"
+ }
+ }' id="payment_" class="admin__field">
+
+
+
+
escapeHtml($details['payerEmail']); ?>
+
+
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 3811461884725..001422d4bf911 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_",
"publicHash": "escapeHtml($block->getData(TokenUiComponentProviderInterface::COMPONENT_PUBLIC_HASH)); ?>",
+ "code": "escapeHtml($block->getData('code')); ?>",
"nonceUrl": "escapeUrl($block->getData('nonceUrl')); ?>"
}
}' id="payment_" 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 fcff173e7fcd4..ea832acb537e0 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 = $('').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 184260c39e954..075a1fdaf1fc1 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 b349d7da577a4..30b0d6f2ac72c 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 @@ public function __construct(
}
/**
+ * 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 @@ public function getOptions()
$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 6f98cd386327a..5c7d5a47da616 100644
--- a/app/code/Magento/Bundle/Setup/InstallSchema.php
+++ b/app/code/Magento/Bundle/Setup/InstallSchema.php
@@ -24,7 +24,9 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con
$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 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con
)
->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 e48ad97922dc9..ced66a03a3a86
--- a/app/code/Magento/Bundle/Setup/UpgradeSchema.php
+++ b/app/code/Magento/Bundle/Setup/UpgradeSchema.php
@@ -44,6 +44,24 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con
}
}
+ 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 362394ccd8bd1..f11fc30f5b28f 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 @@ protected function setUp()
'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 @@ public function testGetJsonConfigFixedPriceBundleNoOption()
];
$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 @@ public function testGetJsonConfigFixedPriceBundle()
$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 @@ public function testGetJsonConfigFixedPriceBundle()
$this->product->expects($this->once())
->method('hasPreconfiguredValues')
- ->will($this->returnValue(true));
+ ->willReturn(true);
$preconfiguredValues = new \Magento\Framework\DataObject(
[
'bundle_option' => [
@@ -217,14 +252,14 @@ public function testGetJsonConfigFixedPriceBundle()
);
$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 @@ public function testGetJsonConfigFixedPriceBundle()
* @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 @@ private function getPriceInfoMock($price)
$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 @@ private function getPriceMock($prices)
foreach ($prices as $methodName => $amount) {
$priceMock->expects($this->any())
->method($methodName)
- ->will($this->returnValue($amount));
+ ->willReturn($amount);
}
return $priceMock;
@@ -373,8 +360,8 @@ private function getAmountPriceMock($value, $baseAmount, array $selectionAmounts
->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 @@ private function createOption(
->disableOriginalConstructor()
->setMethods(
[
- '__wakeup',
'getId',
'getTitle',
'getSelections',
@@ -423,12 +409,12 @@ private function createOption(
'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 @@ private function createOptionSelection(
) {
$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 50a3caaf68b3b..538c80d9b1cf2 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 @@ protected function getBundleOptions()
'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 @@ protected function getBundleOptions()
'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 @@ protected function getBundleOptions()
'selection_qty' => '',
],
'links' => ['insertData' => '${ $.provider }:${ $.dataProvider }'],
- 'source' => 'product',
- 'addButton' => false,
+ 'source' => 'product'
],
],
],
@@ -561,7 +554,7 @@ protected function getBundleSelections()
'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 ab587044476db..24756bdf950a7 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 982a33d00bc6b..34a88d4447cc3 100644
--- a/app/code/Magento/Bundle/etc/module.xml
+++ b/app/code/Magento/Bundle/etc/module.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
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 f0cb656843bca..1272e13e42526 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 @@
?>
-decorateArray($block->getOptions()); ?>
+decorateArray($block->getOptions(true)); ?>
diff --git a/app/code/Magento/Catalog/etc/module.xml b/app/code/Magento/Catalog/etc/module.xml
index 1250b55b96848..0c9e6bb356fe1 100644
--- a/app/code/Magento/Catalog/etc/module.xml
+++ b/app/code/Magento/Catalog/etc/module.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
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 e67bde152475f..a0aec918ccda2 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 4c8ae8eaae952..298cdcc29e953 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 @@
-
-
product_list_toolbar
diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
index c9d155177e48a..fa70427296216 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 @@ public function export()
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 afb2e86b6248a..54208dcdba534 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -2304,7 +2304,7 @@ public function validateRow(array $rowData, $rowNum)
$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 @@ public function validateRow(array $rowData, $rowNum)
$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 7dbd7f4bc5e65..8ed5f43692df5 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 0000000000000..829fa8decda7d
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Product/StockStatusBaseSelectProcessor.php
@@ -0,0 +1,51 @@
+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 0000000000000..4756e42ffe602
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/ResourceModel/Product/StockStatusBaseSelectProcessorTest.php
@@ -0,0 +1,69 @@
+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 254d8d2db4bf9..684f616ecd6fc 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 71b42ef89f73c..e3ca5c01cedab 100644
--- a/app/code/Magento/CatalogInventory/etc/di.xml
+++ b/app/code/Magento/CatalogInventory/etc/di.xml
@@ -79,4 +79,11 @@
Magento\CatalogInventory\Model\Indexer\Stock\Processor
+
+
+
+ - Magento\CatalogInventory\Model\ResourceModel\Product\StockStatusBaseSelectProcessor
+
+
+
diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Product/LinkedProductSelectBuilderByCatalogRulePrice.php
index 7d8d44dcbb68a..55b76eb225028 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 @@ public function __construct(
\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 @@ public function __construct(
$this->dateTime = $dateTime;
$this->localeDate = $localeDate;
$this->metadataPool = $metadataPool;
+ $this->baseSelectProcessor = (null !== $baseSelectProcessor)
+ ? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class);
}
/**
@@ -76,25 +86,28 @@ public function build($productId)
$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 70c5724d446de..cb36f6efbdcde 100644
--- a/app/code/Magento/CatalogRule/Setup/InstallSchema.php
+++ b/app/code/Magento/CatalogRule/Setup/InstallSchema.php
@@ -25,6 +25,9 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con
$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 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con
)
->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 @@ public function install(SchemaSetupInterface $setup, ModuleContextInterface $con
)
->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 1d576e74c4c80..6bfb927c9c8f5 100644
--- a/app/code/Magento/CatalogRule/Setup/UpgradeSchema.php
+++ b/app/code/Magento/CatalogRule/Setup/UpgradeSchema.php
@@ -26,6 +26,20 @@ public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $con
$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 71eb5b49db1fc..004ab82a028d3 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 ea6a730279ed5..d7db233e1eb06 100644
--- a/app/code/Magento/CatalogRule/etc/module.xml
+++ b/app/code/Magento/CatalogRule/etc/module.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
diff --git a/app/code/Magento/CatalogRuleConfigurable/composer.json b/app/code/Magento/CatalogRuleConfigurable/composer.json
index cc51269e2d972..b930380f7bb02 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 9b75e6e6e0c32..ddf86951068ac 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 @@ public function getDataSet(
$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 @@ public function getDataSet(
$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 0000000000000..7099ce2502b19
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Filter/AliasResolver.php
@@ -0,0 +1,44 @@
+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 05205f04f8b99..fb579c1dce29c 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 @@
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\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 @@ public function __construct(
Config $config,
ResourceConnection $resource,
TableMapper $tableMapper,
- $attributePrefix
+ $attributePrefix,
+ ScopeConfigInterface $scopeConfig = null,
+ AliasResolver $aliasResolver = null
) {
$this->conditionManager = $conditionManager;
$this->scopeResolver = $scopeResolver;
@@ -86,7 +100,16 @@ public function __construct(
$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 @@ private function processQueryWithField(FilterInterface $filter, $isNegation, $qu
} 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 @@ private function processRangeNumeric(FilterInterface $filter, $query, $attribute
*/
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 @@ private function processTermSelect(FilterInterface $filter, $isNegation)
$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 0000000000000..8626b2ea15b07
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/ExclusionStrategy.php
@@ -0,0 +1,83 @@
+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 0000000000000..d244e3d5f7548
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterContext.php
@@ -0,0 +1,100 @@
+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 0000000000000..cf17f7d5132ef
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/FilterStrategyInterface.php
@@ -0,0 +1,23 @@
+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 0000000000000..76828fe28f434
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/FilterMapper/TermDropdownStrategy.php
@@ -0,0 +1,127 @@
+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 1d30bb4a14d23..0e96e4de70025 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/IndexBuilder.php
@@ -99,6 +99,7 @@ public function __construct(
*
* @param RequestInterface $request
* @return Select
+ * @throws \LogicException
*/
public function build(RequestInterface $request)
{
@@ -132,7 +133,7 @@ public function build(RequestInterface $request)
),
[]
);
- $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 ca7298e1beaac..ac726192856a5 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\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 @@ public function __construct(
*/
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 @@ private function getFiltersFromBoolFilter(BoolExpression $boolExpression)
}
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 0000000000000..ab01d2553a3ed
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Adapter/Mysql/Filter/AliasResolverTest.php
@@ -0,0 +1,68 @@
+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 0949bf6469be9..2cd6935586bd8 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 @@
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 @@ function ($select) {
)
);
- $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 @@ function ($select) {
'resource' => $resource,
'attributePrefix' => 'attr_',
'metadataPool' => $this->metadataPoolMock,
- 'tableMapper' => $this->tableMapper,
+ 'aliasResolver' => $this->aliasResolver,
]
);
}
@@ -234,7 +235,7 @@ public function testProcessStaticAttribute()
$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 @@ public function testProcessTermFilter($frontendInput, $fieldValue, $isNegation,
->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 0000000000000..9f133b763889b
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/FilterMapper/FilterContextTest.php
@@ -0,0 +1,236 @@
+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 dd9a556146ab3..dad4ad8095f5a 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 @@
*/
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 @@ protected function setUp()
$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 @@ function ($table) {
'storeManager' => $this->storeManager,
'attributeCollectionFactory' => $attributeCollectionFactory,
'eavConfig' => $this->eavConfig,
+ 'aliasResolver' => $this->aliasResolver,
]
);
@@ -160,14 +145,7 @@ public function testAddPriceFilter()
$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 @@ public function testAddStaticAttributeFilter()
{
$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 @@ public function testAddCategoryIds()
$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 @@ public function testAddBoolQueryWithTermFiltersInside()
$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 @@ public function testAddBoolQueryWithTermAndPriceFiltersInside()
$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 @@ public function testAddBoolFilterWithTermFiltersInside()
$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 @@ public function testAddBoolFilterWithBoolFiltersInside()
$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 @@ private function createFilterQuery($filter)
->willReturn(QueryInterface::TYPE_FILTER);
$query->method('getReference')
->willReturn($filter);
+
return $query;
}
@@ -495,6 +312,7 @@ private function createBoolQuery(array $must, array $should, array $mustNot)
->willReturn($should);
$query->method('getMustNot')
->willReturn($mustNot);
+
return $query;
}
@@ -518,6 +336,7 @@ private function createBoolFilter(array $must, array $should, array $mustNot)
->willReturn($should);
$query->method('getMustNot')
->willReturn($mustNot);
+
return $query;
}
@@ -532,6 +351,7 @@ private function createRangeFilter($field)
FilterInterface::TYPE_RANGE,
$field
);
+
return $filter;
}
@@ -546,6 +366,7 @@ private function createTermFilter($field)
FilterInterface::TYPE_TERM,
$field
);
+
return $filter;
}
@@ -564,40 +385,7 @@ private function createFilterMock($class, $type, $field)
->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 49756420bd230..313cc99881a31 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 f62b4e47767a2..7e9451f9b83e7 100644
--- a/app/code/Magento/CatalogSearch/etc/di.xml
+++ b/app/code/Magento/CatalogSearch/etc/di.xml
@@ -11,6 +11,7 @@
+
Magento\CatalogSearch\Model\ResourceModel\EngineInterface::CONFIG_ENGINE_PATH
diff --git a/app/code/Magento/CatalogUrlRewrite/composer.json b/app/code/Magento/CatalogUrlRewrite/composer.json
index 8fd54cbc8ee83..a5f66cd09dda4 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 330f46176286c..198e54db32d88 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 fddfa63b5983e..bfbf0008bf589 100644
--- a/app/code/Magento/Checkout/Controller/Cart/Add.php
+++ b/app/code/Magento/Checkout/Controller/Cart/Add.php
@@ -84,6 +84,7 @@ public function execute()
}
$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 4ce23c9c7f709..236c716f572d9 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 @@ protected function _getProduct($productInfo)
*
* @param \Magento\Framework\DataObject|int|array $requestInfo
* @return \Magento\Framework\DataObject
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function _getProductRequest($requestInfo)
{
@@ -322,11 +328,14 @@ protected function _getProductRequest($requestInfo)
$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 @@ public function updateItem($itemId, $requestInfo = null, $updatingParams = null)
$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 0000000000000..10f3b81386b8f
--- /dev/null
+++ b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilter.php
@@ -0,0 +1,44 @@
+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 0000000000000..2ef24c0a5f28a
--- /dev/null
+++ b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilterComposite.php
@@ -0,0 +1,41 @@
+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 0000000000000..4bd268f6c896e
--- /dev/null
+++ b/app/code/Magento/Checkout/Model/Cart/RequestInfoFilterInterface.php
@@ -0,0 +1,21 @@
+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 @@ public function getPaymentInformation($cartId)
$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 140917dcdfeff..79e76feb43661 100644
--- a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php
+++ b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php
@@ -7,6 +7,9 @@
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 @@ public function savePaymentInformationAndPlaceOrder(
$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 @@ public function getPaymentInformation($cartId)
$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 0000000000000..6c758cf4661fd
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestInfoFilterCompositeTest.php
@@ -0,0 +1,73 @@
+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 0000000000000..cd6ca330da1b2
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Unit/Model/Cart/RequestInfoFilterTest.php
@@ -0,0 +1,52 @@
+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 199e6692e68d1..9984fe12d3792 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 @@ protected function setUp()
'',
false
);
-
$this->stockState = $this->getMock(
\Magento\CatalogInventory\Model\StockState::class,
['suggestQty', '__wakeup'],
@@ -82,12 +104,22 @@ protected function setUp()
'',
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 @@ protected function setUp()
'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 @@ public function testUpdateItems()
*/
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 @@ public function useQtyDataProvider()
['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 093bbf4a5accc..76cbafb48ebd4 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 @@ protected function setUp()
'',
false
);
+ $this->loggerMock = $this->getMock(\Psr\Log\LoggerInterface::class);
$this->model = $objectManager->getObject(
\Magento\Checkout\Model\GuestPaymentInformationManagement::class,
[
@@ -71,6 +77,7 @@ protected function setUp()
'quoteIdMaskFactory' => $this->quoteIdMaskFactoryMock
]
);
+ $objectManager->setBackwardCompatibleProperty($this->model, 'logger', $this->loggerMock);
}
public function testSavePaymentInformationAndPlaceOrder()
@@ -112,7 +119,7 @@ public function testSavePaymentInformationAndPlaceOrderException()
->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 @@ public function testSavePaymentInformationWithoutBillingAddress()
$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 496054b2c7b1e..8da67a1fcb715 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 @@ protected function setUp()
);
$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 @@ protected function setUp()
'cartManagement' => $this->cartManagementMock
]
);
+ $objectManager->setBackwardCompatibleProperty($this->model, 'logger', $this->loggerMock);
}
public function testSavePaymentInformationAndPlaceOrder()
@@ -83,7 +91,8 @@ public function testSavePaymentInformationAndPlaceOrderException()
->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 @@ public function testSavePaymentInformationWithoutBillingAddress()
$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 5545e409b65ab..60919d9fa2ca9 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 c55598fdff5e7..a2243b33a04ed 100644
--- a/app/code/Magento/Checkout/etc/di.xml
+++ b/app/code/Magento/Checkout/etc/di.xml
@@ -26,4 +26,20 @@
+
+
+
+
+ - form_key
+
+
+
+
+
+
+ - Magento\Checkout\Model\Cart\RequestInfoFilter
+
+
+
diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml
index bccf81bcb6ee8..6fb9058c3b768 100644
--- a/app/code/Magento/Checkout/etc/frontend/di.xml
+++ b/app/code/Magento/Checkout/etc/frontend/di.xml
@@ -72,4 +72,12 @@
+
+
+
+ - form_key
+ - custom_price
+
+
+
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 2d6fa460ef66d..3ec34cd96574b 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 ca9d6493a1674..9005015d2460e 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 bee4480b0dfb1..9a3685b212b43 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 0000000000000..4236a215d7359
--- /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 420d50b83478a..c49960ecfb91d 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 18f6b8479c229..918d305ee031b 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 ddb62ceb37b37..8910e41731d11 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 f45115181fe4c..da625c51d4b77 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 @@
+
+
+
+
+