From 8611444a95386d2440505be18a2687c905f0ca06 Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Tue, 7 Aug 2018 15:15:27 +0300 Subject: [PATCH 001/275] MAGETWO-88663: Wrong custom option behavior --- .../Product/View/Options/Type/Select.php | 207 +++++------------- .../Product/View/Options/View/Checkable.php | 61 ++++++ .../Product/View/Options/View/Multiple.php | 103 +++++++++ .../fieldset/options/view/checkable.phtml | 99 +++++++++ 4 files changed, 317 insertions(+), 153 deletions(-) create mode 100644 app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php create mode 100644 app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php create mode 100644 app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php index 7df9b972e1501..209cc52cdaa28 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php @@ -3,8 +3,17 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Catalog\Block\Product\View\Options\Type; +use Magento\Catalog\Model\Product\Option; +use Magento\Catalog\Block\Product\View\Options\View\CheckableFactory; +use Magento\Catalog\Block\Product\View\Options\View\MultipleFactory; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\View\Element\Template\Context; +use Magento\Framework\Pricing\Helper\Data; +use Magento\Catalog\Helper\Data as CatalogHelper; + /** * Product options text type block * @@ -13,169 +22,61 @@ */ class Select extends \Magento\Catalog\Block\Product\View\Options\AbstractOptions { + /** + * @var CheckableFactory + */ + protected $checkableFactory; + /** + * @var MultipleFactory + */ + protected $multipleFactory; + + /** + * Select constructor. + * @param Context $context + * @param Data $pricingHelper + * @param CatalogHelper $catalogData + * @param array $data + * @param CheckableFactory|null $checkableFactory + * @param MultipleFactory|null $multipleFactory + */ + public function __construct( + Context $context, + Data $pricingHelper, + CatalogHelper $catalogData, + array $data = [], + CheckableFactory $checkableFactory = null, + MultipleFactory $multipleFactory = null + ) + { + parent::__construct($context, $pricingHelper, $catalogData, $data); + $this->checkableFactory = $checkableFactory ?: ObjectManager::getInstance()->get(CheckableFactory::class); + $this->multipleFactory = $multipleFactory ?: ObjectManager::getInstance()->get(MultipleFactory::class); + } + /** * Return html for control element * * @return string - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function getValuesHtml() { - $_option = $this->getOption(); - $configValue = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $_option->getId()); - $store = $this->getProduct()->getStore(); - - $this->setSkipJsReloadPrice(1); - // Remove inline prototype onclick and onchange events - - if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN || - $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE + $option = $this->getOption(); + $optionType = $option->getType(); + if ($optionType === Option::OPTION_TYPE_DROP_DOWN || + $optionType === Option::OPTION_TYPE_MULTIPLE ) { - $require = $_option->getIsRequire() ? ' required' : ''; - $extraParams = ''; - $select = $this->getLayout()->createBlock( - \Magento\Framework\View\Element\Html\Select::class - )->setData( - [ - 'id' => 'select_' . $_option->getId(), - 'class' => $require . ' product-custom-option admin__control-select' - ] - ); - if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN) { - $select->setName('options[' . $_option->getId() . ']')->addOption('', __('-- Please Select --')); - } else { - $select->setName('options[' . $_option->getId() . '][]'); - $select->setClass('multiselect admin__control-multiselect' . $require . ' product-custom-option'); - } - foreach ($_option->getValues() as $_value) { - $priceStr = $this->_formatPrice( - [ - 'is_percent' => $_value->getPriceType() == 'percent', - 'pricing_value' => $_value->getPrice($_value->getPriceType() == 'percent'), - ], - false - ); - $select->addOption( - $_value->getOptionTypeId(), - $_value->getTitle() . ' ' . strip_tags($priceStr) . '', - ['price' => $this->pricingHelper->currencyByStore($_value->getPrice(true), $store, false)] - ); - } - if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE) { - $extraParams = ' multiple="multiple"'; - } - if (!$this->getSkipJsReloadPrice()) { - $extraParams .= ' onchange="opConfig.reloadPrice()"'; - } - $extraParams .= ' data-selector="' . $select->getName() . '"'; - $select->setExtraParams($extraParams); - - if ($configValue) { - $select->setValue($configValue); - } - - return $select->getHtml(); + $optionBlock = $this->multipleFactory->create(); } - - if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO || - $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX + if ($optionType === Option::OPTION_TYPE_RADIO || + $optionType === Option::OPTION_TYPE_CHECKBOX ) { - $selectHtml = '
'; - $require = $_option->getIsRequire() ? ' required' : ''; - $arraySign = ''; - switch ($_option->getType()) { - case \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO: - $type = 'radio'; - $class = 'radio admin__control-radio'; - if (!$_option->getIsRequire()) { - $selectHtml .= '
' . - 'getSkipJsReloadPrice() ? '' : ' onclick="opConfig.reloadPrice()"') . - ' value="" checked="checked" />
'; - } - break; - case \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX: - $type = 'checkbox'; - $class = 'checkbox admin__control-checkbox'; - $arraySign = '[]'; - break; - } - $count = 1; - foreach ($_option->getValues() as $_value) { - $count++; - - $priceStr = $this->_formatPrice( - [ - 'is_percent' => $_value->getPriceType() == 'percent', - 'pricing_value' => $_value->getPrice($_value->getPriceType() == 'percent'), - ] - ); - - $htmlValue = $_value->getOptionTypeId(); - if ($arraySign) { - $checked = is_array($configValue) && in_array($htmlValue, $configValue) ? 'checked' : ''; - } else { - $checked = $configValue == $htmlValue ? 'checked' : ''; - } - - $dataSelector = 'options[' . $_option->getId() . ']'; - if ($arraySign) { - $dataSelector .= '[' . $htmlValue . ']'; - } - - $selectHtml .= '
' . - 'getSkipJsReloadPrice() ? '' : ' onclick="opConfig.reloadPrice()"') . - ' name="options[' . - $_option->getId() . - ']' . - $arraySign . - '" id="options_' . - $_option->getId() . - '_' . - $count . - '" value="' . - $htmlValue . - '" ' . - $checked . - ' data-selector="' . $dataSelector . '"' . - ' price="' . - $this->pricingHelper->currencyByStore($_value->getPrice(true), $store, false) . - '" />' . - ''; - $selectHtml .= '
'; - } - $selectHtml .= '
'; - - return $selectHtml; + $optionBlock = $this->checkableFactory->create(); } + return $optionBlock + ->setOption($option) + ->setProduct($this->getProduct()) + ->setSkipJsReloadPrice(1) + ->_toHtml(); } } diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php new file mode 100644 index 0000000000000..c4b73044f718c --- /dev/null +++ b/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php @@ -0,0 +1,61 @@ + $value->getPriceType() === 'percent', + 'pricing_value' => $value->getPrice($value->getPriceType() === 'percent') + ] + ); + } + + /** + * @param ProductCustomOptionValuesInterface $value + * @return float|string + */ + public function getCurrencyByStore(ProductCustomOptionValuesInterface $value) + { + /** @noinspection PhpMethodParametersCountMismatchInspection */ + return $this->pricingHelper->currencyByStore( + $value->getPrice(true), + $this->getProduct()->getStore(), + false + ); + } + + /** + * @param mixed $option + * @return string|array|null + */ + public function getPreconfiguredValue($option) + { + return $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId()); + } +} diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php b/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php new file mode 100644 index 0000000000000..067fc64ca870c --- /dev/null +++ b/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php @@ -0,0 +1,103 @@ +getOption(); + $optionType = $option->getType(); + $configValue = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId()); + $require = $option->getIsRequire() ? ' required' : ''; + $extraParams = ''; + /** @var Select $select */ + $select = $this->getLayout()->createBlock( + Select::class + )->setData( + [ + 'id' => 'select_' . $option->getId(), + 'class' => $require . ' product-custom-option admin__control-select' + ] + ); + $select = $this->insertSelectOption($select, $option); + $select = $this->processSelectOption($select, $option); + if ($optionType === Option::OPTION_TYPE_MULTIPLE) { + $extraParams = ' multiple="multiple"'; + } + if (!$this->getSkipJsReloadPrice()) { + $extraParams .= ' onchange="opConfig.reloadPrice()"'; + } + $extraParams .= ' data-selector="' . $select->getName() . '"'; + $select->setExtraParams($extraParams); + if ($configValue) { + $select->setValue($configValue); + } + return $select->getHtml(); + } + + /** + * @param Select $select + * @param Option $option + * @return Select + */ + private function insertSelectOption(Select $select, Option $option) + { + $require = $option->getIsRequire() ? ' required' : ''; + if ($option->getType() === Option::OPTION_TYPE_DROP_DOWN) { + $select->setName('options[' . $option->getId() . ']')->addOption('', __('-- Please Select --')); + } else { + $select->setName('options[' . $option->getId() . '][]'); + $select->setClass('multiselect admin__control-multiselect' . $require . ' product-custom-option'); + } + return $select; + } + + /** + * @param Select $select + * @param Option $option + * @return Select + */ + private function processSelectOption(Select $select, Option $option) + { + $store = $this->getProduct()->getStore(); + foreach ($option->getValues() as $_value) { + $isPercentPriceType = $_value->getPriceType() === 'percent'; + $priceStr = $this->_formatPrice( + [ + 'is_percent' => $isPercentPriceType, + 'pricing_value' => $_value->getPrice($isPercentPriceType) + ], + false + ); + $select->addOption( + $_value->getOptionTypeId(), + $_value->getTitle() . ' ' . strip_tags($priceStr) . '', + [ + 'price' => $this->pricingHelper->currencyByStore( + $_value->getPrice(true), + $store, + false + ) + ] + ); + } + return $select; + } +} diff --git a/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml new file mode 100644 index 0000000000000..e81bec7603b90 --- /dev/null +++ b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml @@ -0,0 +1,99 @@ +getOption(); +if ($option) : ?> + getPreconfiguredValue($option); + $optionType = $option->getType(); + $arraySign = $optionType === Option::OPTION_TYPE_CHECKBOX ? '[]' : ''; + $count = 1; + ?> + +
+ +
+ + + + + getValues() as $value) : ?> + getOptionTypeId(), $configValue) ? 'checked' : ''; + } else { + $checked = $configValue == $value->getOptionTypeId() ? 'checked' : ''; + } + $dataSelector = 'options[' . $option->getId() . ']'; + if ($arraySign) { + $dataSelector .= '[' . $value->getOptionTypeId() . ']'; + } + ?> + +
+ + data-selector="" + price="getCurrencyByStore($value) ?>" + /> + +
+ +
+ \ No newline at end of file From c333cc1cb676eb257097cd9397a437097097386a Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Tue, 7 Aug 2018 17:14:21 +0300 Subject: [PATCH 002/275] MAGETWO-88663: Wrong custom option behavior --- .../Catalog/Block/Product/View/Options/View/Checkable.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php index c4b73044f718c..bd14493ccf222 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php @@ -8,6 +8,7 @@ use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface; use Magento\Catalog\Block\Product\View\Options\AbstractOptions; +use Magento\Catalog\Model\Product\Option; /** * Class Checkable @@ -51,10 +52,10 @@ public function getCurrencyByStore(ProductCustomOptionValuesInterface $value) } /** - * @param mixed $option + * @param Option $option * @return string|array|null */ - public function getPreconfiguredValue($option) + public function getPreconfiguredValue(Option $option) { return $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId()); } From f9b730019ef70d0852060c4833f5c1fb378599ac Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Wed, 8 Aug 2018 16:55:38 +0300 Subject: [PATCH 003/275] MAGETWO-88663: Wrong custom option behavior --- .../Magento/Catalog/Block/Product/View/Options/Type/Select.php | 3 +-- .../Catalog/Block/Product/View/Options/View/Checkable.php | 2 ++ .../Catalog/Block/Product/View/Options/View/Multiple.php | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php index 209cc52cdaa28..1fa2c18f4ca47 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php @@ -47,8 +47,7 @@ public function __construct( array $data = [], CheckableFactory $checkableFactory = null, MultipleFactory $multipleFactory = null - ) - { + ) { parent::__construct($context, $pricingHelper, $catalogData, $data); $this->checkableFactory = $checkableFactory ?: ObjectManager::getInstance()->get(CheckableFactory::class); $this->multipleFactory = $multipleFactory ?: ObjectManager::getInstance()->get(MultipleFactory::class); diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php index bd14493ccf222..26517be4d93e5 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Block\Product\View\Options\View; use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface; diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php b/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php index 067fc64ca870c..6d1bac8c8c133 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Catalog\Block\Product\View\Options\View; use Magento\Catalog\Block\Product\View\Options\AbstractOptions; From 1ccae207e582192c1c2b566b574c99fd6c8fc5dc Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Thu, 9 Aug 2018 12:39:48 +0300 Subject: [PATCH 004/275] MAGETWO-88663: Wrong custom option behavior --- .../Catalog/Block/Product/View/Options/Type/Select.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php index 1fa2c18f4ca47..84d9ce95cfb7c 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php @@ -25,11 +25,11 @@ class Select extends \Magento\Catalog\Block\Product\View\Options\AbstractOptions /** * @var CheckableFactory */ - protected $checkableFactory; + private $checkableFactory; /** * @var MultipleFactory */ - protected $multipleFactory; + private $multipleFactory; /** * Select constructor. From 1bf405239cb25bcb25a1f3e209420bcd070951f7 Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Thu, 16 Aug 2018 10:53:59 +0300 Subject: [PATCH 005/275] MAGETWO-88663: Wrong custom option behavior --- .../Sales/view/adminhtml/templates/items/column/name.phtml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml b/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml index 30037a918a10c..74604e75a28c1 100644 --- a/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml +++ b/app/code/Magento/Sales/view/adminhtml/templates/items/column/name.phtml @@ -26,7 +26,7 @@ getOrderOptions()): ?>
getOrderOptions() as $_option): ?> -
:
+
escapeHtml($_option['label']) ?>:
getCustomizedOptionValue($_option) ?> From 66f702cafb3543eb0180e59c3dbd71eccf081a75 Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Wed, 22 Aug 2018 12:44:27 +0300 Subject: [PATCH 006/275] MAGETWO-88663: Wrong custom option behavior --- .../Block/Product/View/Options/View/Checkable.php | 5 ++--- .../Catalog/Block/Product/View/Options/View/Multiple.php | 9 +++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php index 26517be4d93e5..c405f4da7b703 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php @@ -13,12 +13,11 @@ use Magento\Catalog\Model\Product\Option; /** - * Class Checkable - * @package Magento\Catalog\Block\Product\View\Options\View + * Represent needed logic for checkbox and radio button + * option types */ class Checkable extends AbstractOptions { - /** @noinspection ClassOverridesFieldOfSuperClassInspection */ /** * @var string */ diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php b/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php index 6d1bac8c8c133..14d67b5a2d914 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php @@ -13,8 +13,7 @@ use Magento\Framework\View\Element\Html\Select; /** - * Class Multiple - * @package Magento\Catalog\Block\Product\View\Options\View + * Represent needed logic for dropdown and multi-select */ class Multiple extends AbstractOptions { @@ -59,7 +58,7 @@ protected function _toHtml() * @param Option $option * @return Select */ - private function insertSelectOption(Select $select, Option $option) + private function insertSelectOption(Select $select, Option $option): Select { $require = $option->getIsRequire() ? ' required' : ''; if ($option->getType() === Option::OPTION_TYPE_DROP_DOWN) { @@ -68,6 +67,7 @@ private function insertSelectOption(Select $select, Option $option) $select->setName('options[' . $option->getId() . '][]'); $select->setClass('multiselect admin__control-multiselect' . $require . ' product-custom-option'); } + return $select; } @@ -76,7 +76,7 @@ private function insertSelectOption(Select $select, Option $option) * @param Option $option * @return Select */ - private function processSelectOption(Select $select, Option $option) + private function processSelectOption(Select $select, Option $option): Select { $store = $this->getProduct()->getStore(); foreach ($option->getValues() as $_value) { @@ -100,6 +100,7 @@ private function processSelectOption(Select $select, Option $option) ] ); } + return $select; } } From 1639b94867410462db0a2f4f1b46a41e29680196 Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Tue, 28 Aug 2018 10:55:23 +0300 Subject: [PATCH 007/275] MAGETWO-88663: Wrong custom option behavior --- .../fieldset/options/view/checkable.phtml | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml index e81bec7603b90..6dbee42dbc88c 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml @@ -23,25 +23,26 @@ if ($option) : ?> $option->getId() ?>-list">
- - + + +
getValues() as $value) : ?> From 69bd35d0573360fdbdae0d7ee8ae96988900d1d5 Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Mon, 3 Sep 2018 12:34:59 +0300 Subject: [PATCH 008/275] MAGETWO-70972: Servers Configurations Needs Update --- .htaccess | 9 +++++++++ .htaccess.sample | 9 +++++++++ nginx.conf.sample | 6 ++++++ pub/.htaccess | 10 ++++++++++ 4 files changed, 34 insertions(+) diff --git a/.htaccess b/.htaccess index d22b5a1395cae..8070910cdccbf 100644 --- a/.htaccess +++ b/.htaccess @@ -364,6 +364,15 @@ Require all denied + + + order allow,deny + deny from all + + = 2.4> + Require all denied + + # For 404s and 403s that aren't handled by the application, show plain 404 response ErrorDocument 404 /pub/errors/404.php diff --git a/.htaccess.sample b/.htaccess.sample index c9ddff2cca4cf..7db221343c4bc 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -341,6 +341,15 @@ Require all denied + + + order allow,deny + deny from all + + = 2.4> + Require all denied + + # For 404s and 403s that aren't handled by the application, show plain 404 response ErrorDocument 404 /pub/errors/404.php diff --git a/nginx.conf.sample b/nginx.conf.sample index 6f87a9a076666..41f16bea857ad 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -33,6 +33,12 @@ charset UTF-8; error_page 404 403 = /errors/404.php; #add_header "X-UA-Compatible" "IE=Edge"; + +# Deny access to sensitive files +location /.user.ini { + deny all; +} + # PHP entry point for setup application location ~* ^/setup($|/) { root $MAGE_ROOT; diff --git a/pub/.htaccess b/pub/.htaccess index 8ba04ff4415f3..85a204c85e8a8 100644 --- a/pub/.htaccess +++ b/pub/.htaccess @@ -220,6 +220,16 @@ ErrorDocument 403 /errors/404.php Require all denied +## Deny access to .user.ini + + + order allow,deny + deny from all + + = 2.4> + Require all denied + + ############################################ From f2f0e47869026f911e790fa2732e286a4984fb5c Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Mon, 3 Sep 2018 14:43:25 +0300 Subject: [PATCH 009/275] MAGETWO-88607: Wrong behavior of static content deploy --- .../Magento/Deploy/Console/InputValidator.php | 42 +++- .../Deploy/Service/DeployStaticContent.php | 2 + .../Test/Unit/Console/InputValidatorTest.php | 208 ++++++++++++++++++ 3 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php diff --git a/app/code/Magento/Deploy/Console/InputValidator.php b/app/code/Magento/Deploy/Console/InputValidator.php index b3301f60fec26..2ce9712a78729 100644 --- a/app/code/Magento/Deploy/Console/InputValidator.php +++ b/app/code/Magento/Deploy/Console/InputValidator.php @@ -9,6 +9,8 @@ use Magento\Deploy\Console\DeployStaticOptions as Options; use Magento\Framework\Validator\Locale; use Symfony\Component\Console\Input\InputInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\Validator\RegexFactory; /** * Command input arguments validator class @@ -55,14 +57,24 @@ class InputValidator */ private $localeValidator; + /** + * @var RegexFactory + */ + private $versionValidatorFactory; + /** * InputValidator constructor * * @param Locale $localeValidator + * @param RegexFactory $versionValidatorFactory */ - public function __construct(Locale $localeValidator) - { + public function __construct( + Locale $localeValidator, + ?RegexFactory $versionValidatorFactory = null + ) { $this->localeValidator = $localeValidator; + $this->versionValidatorFactory = $versionValidatorFactory ?: + ObjectManager::getInstance()->get(RegexFactory::class); } /** @@ -85,6 +97,9 @@ public function validate(InputInterface $input) $input->getArgument(Options::LANGUAGES_ARGUMENT) ?: ['all'], $input->getOption(Options::EXCLUDE_LANGUAGE) ); + $this->checkVersionInput( + $input->getOption(Options::CONTENT_VERSION) ?: '' + ); } /** @@ -147,4 +162,27 @@ private function checkLanguagesInput(array $languagesInclude, array $languagesEx } } } + + /** + * @param string $contentVersion + * @throws \InvalidArgumentException + */ + private function checkVersionInput(string $contentVersion): void + { + if ($contentVersion) { + $versionValidator = $this->versionValidatorFactory->create( + [ + 'pattern' => '/^[A-Za-z0-9_.]+$/' + ] + ); + + if (!$versionValidator->isValid($contentVersion)) { + throw new \InvalidArgumentException( + 'Argument "' . + Options::CONTENT_VERSION + . '" has invalid value, content version should contain only characters, digits and dots' + ); + } + } + } } diff --git a/app/code/Magento/Deploy/Service/DeployStaticContent.php b/app/code/Magento/Deploy/Service/DeployStaticContent.php index 66ec6e7418afd..ed36a01edebd4 100644 --- a/app/code/Magento/Deploy/Service/DeployStaticContent.php +++ b/app/code/Magento/Deploy/Service/DeployStaticContent.php @@ -9,6 +9,7 @@ use Magento\Deploy\Process\QueueFactory; use Magento\Deploy\Console\DeployStaticOptions as Options; use Magento\Framework\App\View\Deployment\Version\StorageInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\ObjectManagerInterface; use Psr\Log\LoggerInterface; @@ -71,6 +72,7 @@ public function __construct( * Run deploy procedure * * @param array $options + * @throws LocalizedException * @return void */ public function deploy(array $options) diff --git a/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php new file mode 100644 index 0000000000000..ab540d11f6318 --- /dev/null +++ b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php @@ -0,0 +1,208 @@ +objectManagerHelper = new ObjectManagerHelper($this); + + $regexFactoryMock = $this->getMockBuilder(RegexFactory::class) + ->disableOriginalConstructor() + ->setMethods(['create']) + ->getMock(); + + $regexObject = new Regex('/^[A-Za-z0-9_.]+$/'); + + $regexFactoryMock->expects($this->any())->method('create') + ->willReturn($regexObject); + + $localeObjectMock = $this->getMockBuilder(Locale::class)->setMethods(['isValid']) + ->disableOriginalConstructor() + ->getMock(); + + $localeObjectMock->expects($this->any())->method('isValid') + ->with('en_US') + ->will($this->returnValue(true)); + + $this->inputValidator = $this->objectManagerHelper->getObject( + InputValidator::class, + [ + 'localeValidator' => $localeObjectMock, + 'versionValidatorFactory' => $regexFactoryMock + ] + ); + } + + /** + * @throws \Zend_Validate_Exception + */ + public function testValidate() + { + $input = $this->getMockBuilder(ArrayInput::class) + ->disableOriginalConstructor() + ->setMethods(['getOption', 'getArgument']) + ->getMock(); + + $input->expects($this->atLeastOnce())->method('getArgument')->willReturn(['all']); + + $input->expects($this->atLeastOnce())->method('getOption') + ->willReturnMap( + [ + [Options::AREA, ['all']], + [Options::EXCLUDE_AREA, ['none']], + [Options::THEME, ['all']], + [Options::EXCLUDE_THEME, ['none']], + [Options::EXCLUDE_LANGUAGE, ['none']], + [Options::CONTENT_VERSION, '12345'] + ] + ); + + /** @noinspection PhpParamsInspection */ + $this->inputValidator->validate($input); + } + + /** + * @covers \Magento\Deploy\Console\InputValidator::checkAreasInput() + */ + public function testCheckAreasInputException() + { + $options = [ + new InputOption(Options::AREA, null, 4, '', ['test']), + new InputOption(Options::EXCLUDE_AREA, null, 4, '', ['test']) + ]; + + $inputDefinition = new InputDefinition($options); + + try { + $this->inputValidator->validate( + new ArrayInput([], $inputDefinition) + ); + } catch (\Exception $e) { + $this->assertContains('--area (-a) and --exclude-area cannot be used at the same time', $e->getMessage()); + $this->assertInstanceOf(InvalidArgumentException::class, $e); + } + } + + /** + * @covers \Magento\Deploy\Console\InputValidator::checkThemesInput() + */ + public function testCheckThemesInputException() + { + $options = [ + new InputOption(Options::AREA, null, 4, '', ['all']), + new InputOption(Options::EXCLUDE_AREA, null, 4, '', ['none']), + new InputOption(Options::THEME, null, 4, '', ['blank']), + new InputOption(Options::EXCLUDE_THEME, null, 4, '', ['luma']) + ]; + + $inputDefinition = new InputDefinition($options); + + try { + $this->inputValidator->validate( + new ArrayInput([], $inputDefinition) + ); + } catch (\Exception $e) { + $this->assertContains('--theme (-t) and --exclude-theme cannot be used at the same time', $e->getMessage()); + $this->assertInstanceOf(InvalidArgumentException::class, $e); + } + } + + public function testCheckLanguagesInputException() + { + $options = [ + new InputOption(Options::AREA, null, 4, '', ['all']), + new InputOption(Options::EXCLUDE_AREA, '', 4, '', ['none']), + new InputOption(Options::THEME, null, 4, '', ['all']), + new InputOption(Options::EXCLUDE_THEME, null, 4, '', ['none']), + new InputArgument(Options::LANGUAGES_ARGUMENT, null, 4, ['en_US']), + new InputOption(Options::EXCLUDE_LANGUAGE, null, 4, '', ['all']) + ]; + + $inputDefinition = new InputDefinition($options); + + try { + $this->inputValidator->validate( + new ArrayInput([], $inputDefinition) + ); + } catch (\Exception $e) { + $this->assertContains( + '--language (-l) and --exclude-language cannot be used at the same time', + $e->getMessage() + ); + + $this->assertInstanceOf(InvalidArgumentException::class, $e); + } + } + + public function testCheckVersionInputException() + { + $options = [ + new InputOption(Options::AREA, null, 4, '', ['all']), + new InputOption(Options::EXCLUDE_AREA, null, 4, '', ['none']), + new InputOption(Options::THEME, null, 4, '', ['all']), + new InputOption(Options::EXCLUDE_THEME, null, 4, '', ['none']), + new InputArgument(Options::LANGUAGES_ARGUMENT, null, 4, ['en_US']), + new InputOption(Options::EXCLUDE_LANGUAGE, null, 4, '', ['none']), + new InputOption(Options::CONTENT_VERSION, null, 4, '', '/*!#') + ]; + + $inputDefinition = new InputDefinition($options); + + try { + $this->inputValidator->validate( + new ArrayInput([], $inputDefinition) + ); + } catch (\Exception $e) { + $this->assertContains( + 'Argument "' . + Options::CONTENT_VERSION + . '" has invalid value, content version should contain only characters, digits and dots', + $e->getMessage() + ); + + $this->assertInstanceOf(InvalidArgumentException::class, $e); + } + } +} From 411b4820c9d3a0e860bb6670ba4ae07c33058a72 Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Mon, 3 Sep 2018 14:48:49 +0300 Subject: [PATCH 010/275] MAGETWO-88607: Wrong behavior of static content deploy --- app/code/Magento/Deploy/Service/DeployStaticContent.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Deploy/Service/DeployStaticContent.php b/app/code/Magento/Deploy/Service/DeployStaticContent.php index ed36a01edebd4..c8885caf25422 100644 --- a/app/code/Magento/Deploy/Service/DeployStaticContent.php +++ b/app/code/Magento/Deploy/Service/DeployStaticContent.php @@ -17,6 +17,7 @@ * Main service for static content deployment * * Aggregates services to deploy static files, static files bundles, translations and minified templates + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class DeployStaticContent { From 52c08f1dc5716922208cbfeaf9ccaf30f6563dca Mon Sep 17 00:00:00 2001 From: Roman Leshchenko Date: Mon, 3 Sep 2018 14:51:21 +0300 Subject: [PATCH 011/275] MAGETWO-88607: Wrong behavior of static content deploy --- .../Magento/Deploy/Test/Unit/Console/InputValidatorTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php index ab540d11f6318..3ed2e48710c1d 100644 --- a/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php @@ -4,6 +4,8 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Deploy\Test\Unit\Console; use Magento\Framework\Validator\Regex; From 00178e76e384c29269ac7cec7ee626a8ccce5b8a Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Mon, 3 Sep 2018 17:11:43 +0300 Subject: [PATCH 012/275] MAGETWO-71993: CMS Image Upload Response Contains Redundant Info --- .../Adminhtml/Wysiwyg/Images/Upload.php | 16 ++++++++++--- .../Cms/Model/Wysiwyg/Images/Storage.php | 11 +++------ .../Adminhtml/Wysiwyg/Images/UploadTest.php | 23 ++++++++++++++++++- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php index 5c9aa2243bc6d..81a7af7e76599 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/Upload.php @@ -4,6 +4,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; use Magento\Framework\App\Filesystem\DirectoryList; @@ -57,13 +60,20 @@ public function execute() __('Directory %1 is not under storage root path.', $path) ); } - $result = $this->getStorage()->uploadFile($path, $this->getRequest()->getParam('type')); + $uploaded = $this->getStorage()->uploadFile($path, $this->getRequest()->getParam('type')); + $response = [ + 'name' => $uploaded['name'], + 'type' => $uploaded['type'], + 'error' => $uploaded['error'], + 'size' => $uploaded['size'], + 'file' => $uploaded['file'] + ]; } catch (\Exception $e) { - $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; + $response = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()]; } /** @var \Magento\Framework\Controller\Result\Json $resultJson */ $resultJson = $this->resultJsonFactory->create(); - return $resultJson->setData($result); + return $resultJson->setData($response); } } diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php index dfd55f5346e59..c1dfe993141a5 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -3,6 +3,9 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Cms\Model\Wysiwyg\Images; use Magento\Cms\Helper\Wysiwyg\Images; @@ -501,14 +504,6 @@ public function uploadFile($targetPath, $type = null) // create thumbnail $this->resizeFile($targetPath . '/' . $uploader->getUploadedFileName(), true); - $result['cookie'] = [ - 'name' => $this->getSession()->getName(), - 'value' => $this->getSession()->getSessionId(), - 'lifetime' => $this->getSession()->getCookieLifetime(), - 'path' => $this->getSession()->getCookiePath(), - 'domain' => $this->getSession()->getCookieDomain(), - ]; - return $result; } diff --git a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/UploadTest.php b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/UploadTest.php index bab14a8663eae..b0647efbd8204 100644 --- a/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/UploadTest.php +++ b/dev/tests/integration/testsuite/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/UploadTest.php @@ -4,9 +4,14 @@ * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Controller\Result\Json as JsonResponse; +use Magento\Framework\App\Response\HttpFactory as ResponseFactory; +use Magento\Framework\App\Response\Http as Response; /** * Test for \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images\Upload class. @@ -43,6 +48,11 @@ class UploadTest extends \PHPUnit\Framework\TestCase */ private $objectManager; + /** + * @var HttpFactory + */ + private $responseFactory; + /** * @inheritdoc */ @@ -56,6 +66,7 @@ protected function setUp() $this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); $this->fullDirectoryPath = $imagesHelper->getStorageRoot() . DIRECTORY_SEPARATOR . $directoryName; $this->mediaDirectory->create($this->mediaDirectory->getRelativePath($this->fullDirectoryPath)); + $this->responseFactory = $this->objectManager->get(ResponseFactory::class); $this->model = $this->objectManager->get(\Magento\Cms\Controller\Adminhtml\Wysiwyg\Images\Upload::class); $fixtureDir = realpath(__DIR__ . '/../../../../../Catalog/_files'); $tmpFile = $this->filesystem->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath() . $this->fileName; @@ -82,7 +93,11 @@ public function testExecute() { $this->model->getRequest()->setParams(['type' => 'image/png']); $this->model->getStorage()->getSession()->setCurrentPath($this->fullDirectoryPath); - $this->model->execute(); + /** @var JsonResponse $jsonResponse */ + $jsonResponse = $this->model->execute(); + /** @var Response $response */ + $jsonResponse->renderResult($response = $this->responseFactory->create()); + $data = json_decode($response->getBody(), true); $this->assertTrue( $this->mediaDirectory->isExist( @@ -91,6 +106,12 @@ public function testExecute() ) ) ); + //Asserting that response contains only data needed by clients. + $keys = ['name', 'type', 'error', 'size', 'file']; + sort($keys); + $dataKeys = array_keys($data); + sort($dataKeys); + $this->assertEquals($keys, $dataKeys); } /** From 68206982c01623806e45fe7e5b376316601b6db8 Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Tue, 4 Sep 2018 11:57:02 +0300 Subject: [PATCH 013/275] MAGETWO-71993: CMS Image Upload Response Contains Redundant Info --- .../Unit/Model/Wysiwyg/Images/StorageTest.php | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php index 2dc98bcefb961..33758bd44d141 100644 --- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php +++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php @@ -441,13 +441,6 @@ public function testUploadFile() $type = 'image'; $result = [ 'result', - 'cookie' => [ - 'name' => 'session_name', - 'value' => '1', - 'lifetime' => '50', - 'path' => 'cookie/path', - 'domain' => 'cookie_domain', - ], ]; $uploader = $this->getMockBuilder(\Magento\MediaStorage\Model\File\Uploader::class) ->disableOriginalConstructor() @@ -507,17 +500,6 @@ public function testUploadFile() $this->adapterFactoryMock->expects($this->atLeastOnce())->method('create')->willReturn($image); - $this->sessionMock->expects($this->atLeastOnce())->method('getName') - ->willReturn($result['cookie']['name']); - $this->sessionMock->expects($this->atLeastOnce())->method('getSessionId') - ->willReturn($result['cookie']['value']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookieLifetime') - ->willReturn($result['cookie']['lifetime']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookiePath') - ->willReturn($result['cookie']['path']); - $this->sessionMock->expects($this->atLeastOnce())->method('getCookieDomain') - ->willReturn($result['cookie']['domain']); - $this->assertEquals($result, $this->imagesStorage->uploadFile($targetPath, $type)); } } From 963d3a1b3061dbc751ef577c8d7ff06776c0e2f0 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 6 Sep 2018 17:19:18 +0300 Subject: [PATCH 014/275] MAGETWO-88663: Wrong custom option behavior --- .../Catalog/Block/Product/View/Options/Type/Select.php | 4 ++-- .../Product/View/Options/{View => Type/Select}/Checkable.php | 2 +- .../Product/View/Options/{View => Type/Select}/Multiple.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename app/code/Magento/Catalog/Block/Product/View/Options/{View => Type/Select}/Checkable.php (96%) rename app/code/Magento/Catalog/Block/Product/View/Options/{View => Type/Select}/Multiple.php (98%) diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php index 84d9ce95cfb7c..d9d663b32f4de 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php @@ -7,8 +7,8 @@ namespace Magento\Catalog\Block\Product\View\Options\Type; use Magento\Catalog\Model\Product\Option; -use Magento\Catalog\Block\Product\View\Options\View\CheckableFactory; -use Magento\Catalog\Block\Product\View\Options\View\MultipleFactory; +use Magento\Catalog\Block\Product\View\Options\Type\Select\CheckableFactory; +use Magento\Catalog\Block\Product\View\Options\Type\Select\MultipleFactory; use Magento\Framework\App\ObjectManager; use Magento\Framework\View\Element\Template\Context; use Magento\Framework\Pricing\Helper\Data; diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php similarity index 96% rename from app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php rename to app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php index c405f4da7b703..f516072c1c83f 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/View/Checkable.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php @@ -6,7 +6,7 @@ declare(strict_types=1); -namespace Magento\Catalog\Block\Product\View\Options\View; +namespace Magento\Catalog\Block\Product\View\Options\Type\Select; use Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface; use Magento\Catalog\Block\Product\View\Options\AbstractOptions; diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php similarity index 98% rename from app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php rename to app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php index 14d67b5a2d914..84da8a018800f 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/View/Multiple.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php @@ -6,7 +6,7 @@ declare(strict_types=1); -namespace Magento\Catalog\Block\Product\View\Options\View; +namespace Magento\Catalog\Block\Product\View\Options\Type\Select; use Magento\Catalog\Block\Product\View\Options\AbstractOptions; use Magento\Catalog\Model\Product\Option; From d8eacadc25cd6a98fbaf646ca39c9bb9ebb9f5de Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 7 Sep 2018 14:22:03 +0300 Subject: [PATCH 015/275] MAGETWO-90467: Added posibility to use captcha on share wishlist page --- .../Wishlist/Block/Customer/Sharing.php | 16 + .../Wishlist/Controller/Index/Send.php | 83 ++- .../Test/Unit/Controller/Index/SendTest.php | 568 ++---------------- app/code/Magento/Wishlist/composer.json | 3 +- app/code/Magento/Wishlist/etc/config.xml | 16 + app/code/Magento/Wishlist/etc/module.xml | 1 + .../view/frontend/templates/sharing.phtml | 1 + .../AssertCaptchaFieldOnContactUsForm.php | 2 +- .../Captcha/Test/Page/ContactIndex.xml | 2 +- .../Test/TestCase/CaptchaOnContactUsTest.php | 2 +- .../Magento/Customer/Controller/SendTest.php | 62 ++ 11 files changed, 241 insertions(+), 515 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Customer/Controller/SendTest.php diff --git a/app/code/Magento/Wishlist/Block/Customer/Sharing.php b/app/code/Magento/Wishlist/Block/Customer/Sharing.php index 6fbf5a23dca22..992946363186c 100644 --- a/app/code/Magento/Wishlist/Block/Customer/Sharing.php +++ b/app/code/Magento/Wishlist/Block/Customer/Sharing.php @@ -11,6 +11,8 @@ */ namespace Magento\Wishlist\Block\Customer; +use Magento\Captcha\Block\Captcha; + /** * @api * @since 100.0.2 @@ -60,6 +62,20 @@ public function __construct( */ protected function _prepareLayout() { + if (!$this->getChildBlock('captcha')) { + $this->addChild( + 'captcha', + Captcha::class, + [ + 'cacheable' => false, + 'after' => '-', + 'form_id' => 'share_wishlist_form', + 'image_width' => 230, + 'image_height' => 230 + ] + ); + } + $this->pageConfig->getTitle()->set(__('Wish List Sharing')); } diff --git a/app/code/Magento/Wishlist/Controller/Index/Send.php b/app/code/Magento/Wishlist/Controller/Index/Send.php index c2389af6a2282..4c53aaac40916 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Send.php +++ b/app/code/Magento/Wishlist/Controller/Index/Send.php @@ -8,11 +8,20 @@ use Magento\Framework\App\Action; use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ResponseInterface; use Magento\Framework\Exception\NotFoundException; use Magento\Framework\Session\Generic as WishlistSession; use Magento\Store\Model\StoreManagerInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\View\Result\Layout as ResultLayout; +use Magento\Captcha\Helper\Data as CaptchaHelper; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\App\ObjectManager; +use Magento\Captcha\Model\DefaultModel as CaptchaModel; +use Magento\Framework\Exception\LocalizedException; +use Magento\Customer\Model\Customer; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -69,6 +78,16 @@ class Send extends \Magento\Wishlist\Controller\AbstractIndex */ protected $storeManager; + /** + * @var CaptchaHelper + */ + private $captchaHelper; + + /** + * @var CaptchaStringResolver + */ + private $captchaStringResolver; + /** * @param Action\Context $context * @param \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator @@ -81,6 +100,8 @@ class Send extends \Magento\Wishlist\Controller\AbstractIndex * @param WishlistSession $wishlistSession * @param ScopeConfigInterface $scopeConfig * @param StoreManagerInterface $storeManager + * @param CaptchaHelper|null $captchaHelper + * @param CaptchaStringResolver|null $captchaStringResolver * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -94,7 +115,9 @@ public function __construct( \Magento\Customer\Helper\View $customerHelperView, WishlistSession $wishlistSession, ScopeConfigInterface $scopeConfig, - StoreManagerInterface $storeManager + StoreManagerInterface $storeManager, + ?CaptchaHelper $captchaHelper = null, + ?CaptchaStringResolver $captchaStringResolver = null ) { $this->_formKeyValidator = $formKeyValidator; $this->_customerSession = $customerSession; @@ -106,6 +129,10 @@ public function __construct( $this->wishlistSession = $wishlistSession; $this->scopeConfig = $scopeConfig; $this->storeManager = $storeManager; + $this->captchaHelper = $captchaHelper ?: ObjectManager::getInstance()->get(CaptchaHelper::class); + $this->captchaStringResolver = $captchaStringResolver ? + : ObjectManager::getInstance()->get(CaptchaStringResolver::class); + parent::__construct($context); } @@ -114,6 +141,7 @@ public function __construct( * * @return \Magento\Framework\Controller\Result\Redirect * @throws NotFoundException + * @throws \Zend_Validate_Exception * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -122,11 +150,25 @@ public function execute() { /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + $captchaForName = 'share_wishlist_form'; + /** @var CaptchaModel $captchaModel */ + $captchaModel = $this->captchaHelper->getCaptcha($captchaForName); + if (!$this->_formKeyValidator->validate($this->getRequest())) { $resultRedirect->setPath('*/*/'); return $resultRedirect; } + $isCorrectCaptcha = $this->validateCaptcha($captchaModel, $captchaForName); + + $this->logCaptchaAttempt($captchaModel); + + if (!$isCorrectCaptcha) { + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); + $resultRedirect->setPath('*/*/share'); + return $resultRedirect; + } + $wishlist = $this->wishlistProvider->getWishlist(); if (!$wishlist) { throw new NotFoundException(__('Page not found.')); @@ -288,4 +330,43 @@ protected function getWishlistItems(ResultLayout $resultLayout) ->getBlock('wishlist.email.items') ->toHtml(); } + + /** + * Log customer action attempts + * @param CaptchaModel $captchaModel + * @return void + */ + private function logCaptchaAttempt(CaptchaModel $captchaModel) + { + /** @var Customer $customer */ + $customer = $this->_customerSession->getCustomer(); + $email = ''; + + if ($customer->getId()) { + $email = $customer->getEmail(); + } + + $captchaModel->logAttempt($email); + } + + /** + * @param CaptchaModel $captchaModel + * @param string $captchaFormName + * @return bool + */ + private function validateCaptcha(CaptchaModel $captchaModel, string $captchaFormName) : bool + { + if ($captchaModel->isRequired()) { + $word = $this->captchaStringResolver->resolve( + $this->getRequest(), + $captchaFormName + ); + + if (!$captchaModel->isCorrect($word)) { + return false; + } + } + + return true; + } } diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/SendTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/SendTest.php index a8c0fbb951cce..47148f7878134 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/SendTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/SendTest.php @@ -5,32 +5,24 @@ */ namespace Magento\Wishlist\Test\Unit\Controller\Index; -use Magento\Customer\Helper\View as CustomerViewHelper; use Magento\Customer\Model\Data\Customer as CustomerData; -use Magento\Customer\Model\Session as CustomerSession; use Magento\Framework\App\Action\Context as ActionContext; -use Magento\Framework\App\Area; -use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\RequestInterface; use Magento\Framework\Controller\Result\Redirect as ResultRedirect; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Data\Form\FormKey\Validator as FormKeyValidator; use Magento\Framework\Event\ManagerInterface as EventManagerInterface; -use Magento\Framework\Mail\Template\TransportBuilder; use Magento\Framework\Mail\TransportInterface; use Magento\Framework\Message\ManagerInterface; -use Magento\Framework\Session\Generic as WishlistSession; -use Magento\Framework\Translate\Inline\StateInterface as TranslateInlineStateInterface; use Magento\Framework\UrlInterface; -use Magento\Framework\View\Layout; use Magento\Framework\View\Result\Layout as ResultLayout; -use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; -use Magento\Store\Model\StoreManagerInterface; use Magento\Wishlist\Controller\Index\Send; use Magento\Wishlist\Controller\WishlistProviderInterface; -use Magento\Wishlist\Model\Config as WishlistConfig; -use Magento\Wishlist\Model\Wishlist; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Captcha\Helper\Data as CaptchaHelper; +use Magento\Captcha\Model\DefaultModel as CaptchaModel; +use Magento\Customer\Model\Session; /** * @SuppressWarnings(PHPMD.TooManyFields) @@ -47,36 +39,12 @@ class SendTest extends \PHPUnit\Framework\TestCase /** @var FormKeyValidator |\PHPUnit_Framework_MockObject_MockObject */ protected $formKeyValidator; - /** @var CustomerSession |\PHPUnit_Framework_MockObject_MockObject */ - protected $customerSession; - /** @var WishlistProviderInterface |\PHPUnit_Framework_MockObject_MockObject */ protected $wishlistProvider; - /** @var WishlistConfig |\PHPUnit_Framework_MockObject_MockObject */ - protected $wishlistConfig; - - /** @var TransportBuilder |\PHPUnit_Framework_MockObject_MockObject */ - protected $transportBuilder; - - /** @var TranslateInlineStateInterface |\PHPUnit_Framework_MockObject_MockObject */ - protected $inlineTranslation; - - /** @var CustomerViewHelper |\PHPUnit_Framework_MockObject_MockObject */ - protected $customerViewHelper; - - /** @var WishlistSession |\PHPUnit_Framework_MockObject_MockObject */ - protected $wishlistSession; - - /** @var ScopeConfigInterface |\PHPUnit_Framework_MockObject_MockObject */ - protected $scopeConfig; - /** @var Store |\PHPUnit_Framework_MockObject_MockObject */ protected $store; - /** @var StoreManagerInterface |\PHPUnit_Framework_MockObject_MockObject */ - protected $storeManager; - /** @var ResultFactory |\PHPUnit_Framework_MockObject_MockObject */ protected $resultFactory; @@ -86,15 +54,9 @@ class SendTest extends \PHPUnit\Framework\TestCase /** @var ResultLayout |\PHPUnit_Framework_MockObject_MockObject */ protected $resultLayout; - /** @var Layout |\PHPUnit_Framework_MockObject_MockObject */ - protected $layout; - /** @var RequestInterface |\PHPUnit_Framework_MockObject_MockObject */ protected $request; - /** @var Wishlist |\PHPUnit_Framework_MockObject_MockObject */ - protected $wishlist; - /** @var ManagerInterface |\PHPUnit_Framework_MockObject_MockObject */ protected $messageManager; @@ -110,6 +72,15 @@ class SendTest extends \PHPUnit\Framework\TestCase /** @var EventManagerInterface |\PHPUnit_Framework_MockObject_MockObject */ protected $eventManager; + /** @var CaptchaHelper |\PHPUnit_Framework_MockObject_MockObject */ + protected $captchaHelper; + + /** @var CaptchaModel |\PHPUnit_Framework_MockObject_MockObject */ + protected $captchaModel; + + /** @var Session |\PHPUnit_Framework_MockObject_MockObject */ + protected $customerSession; + /** * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ @@ -136,7 +107,7 @@ protected function setUp() $this->request = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) ->setMethods([ 'getPost', - 'getPostValue', + 'getPostValue' ]) ->getMockForAbstractClass(); @@ -172,90 +143,72 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); - $this->customerSession = $this->getMockBuilder(\Magento\Customer\Model\Session::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->wishlistProvider = $this->getMockBuilder(\Magento\Wishlist\Controller\WishlistProviderInterface::class) - ->getMockForAbstractClass(); - - $this->wishlistConfig = $this->getMockBuilder(\Magento\Wishlist\Model\Config::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->transportBuilder = $this->getMockBuilder(\Magento\Framework\Mail\Template\TransportBuilder::class) + $customerMock = $this->getMockBuilder(\Magento\Customer\Model\Customer::class) ->disableOriginalConstructor() + ->setMethods([ + 'getEmail', + 'getId' + ]) ->getMock(); - $this->inlineTranslation = $this->getMockBuilder(\Magento\Framework\Translate\Inline\StateInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $customerMock->expects($this->any()) + ->method('getEmail') + ->willReturn('expamle@mail.com'); - $this->customerViewHelper = $this->getMockBuilder(\Magento\Customer\Helper\View::class) - ->disableOriginalConstructor() - ->getMock(); + $customerMock->expects($this->any()) + ->method('getId') + ->willReturn(false); - $this->wishlistSession = $this->getMockBuilder(\Magento\Framework\Session\Generic::class) + $this->customerSession = $this->getMockBuilder(\Magento\Customer\Model\Session::class) ->disableOriginalConstructor() - ->setMethods(['setSharingForm']) + ->setMethods([ + 'getCustomer', + 'getData' + ]) ->getMock(); - $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class) - ->disableOriginalConstructor() - ->getMock(); + $this->customerSession->expects($this->any()) + ->method('getCustomer') + ->willReturn($customerMock); - $this->store = $this->getMockBuilder(\Magento\Store\Model\Store::class) - ->disableOriginalConstructor() - ->setMethods(['getStoreId']) - ->getMock(); + $this->customerSession->expects($this->any()) + ->method('getData') + ->willReturn(false); - $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class) - ->disableOriginalConstructor() - ->getMock(); - $this->storeManager->expects($this->any()) - ->method('getStore') - ->willReturn($this->store); + $this->wishlistProvider = $this->getMockBuilder(\Magento\Wishlist\Controller\WishlistProviderInterface::class) + ->getMockForAbstractClass(); - $this->wishlist = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class) + $this->captchaHelper = $this->getMockBuilder(CaptchaHelper::class) ->disableOriginalConstructor() ->setMethods([ - 'getShared', - 'setShared', - 'getId', - 'getSharingCode', - 'save', - 'isSalable', + 'getCaptcha' ]) ->getMock(); - $this->customerData = $this->getMockBuilder(\Magento\Customer\Model\Data\Customer::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->layout = $this->getMockBuilder(\Magento\Framework\View\Layout::class) + $this->captchaModel = $this->getMockBuilder(CaptchaModel::class) ->disableOriginalConstructor() ->setMethods([ - 'getBlock', - 'setWishlistId', - 'toHtml', + 'isRequired', + 'logAttempt' ]) ->getMock(); - $this->transport = $this->getMockBuilder(\Magento\Framework\Mail\TransportInterface::class) - ->getMockForAbstractClass(); + $objectHelper = new ObjectManager($this); + + $this->captchaHelper->expects($this->once())->method('getCaptcha') + ->willReturn($this->captchaModel); + $this->captchaModel->expects($this->any())->method('isRequired') + ->willReturn(false); - $this->model = new Send( - $this->context, - $this->formKeyValidator, - $this->customerSession, - $this->wishlistProvider, - $this->wishlistConfig, - $this->transportBuilder, - $this->inlineTranslation, - $this->customerViewHelper, - $this->wishlistSession, - $this->scopeConfig, - $this->storeManager + $this->model = $objectHelper->getObject( + Send::class, + [ + 'context' => $this->context, + 'formKeyValidator' => $this->formKeyValidator, + 'wishlistProvider' => $this->wishlistProvider, + 'captchaHelper' => $this->captchaHelper, + '_customerSession' => $this->customerSession + ] ); } @@ -291,409 +244,4 @@ public function testExecuteNoWishlistAvailable() $this->model->execute(); } - - /** - * @param string $text - * @param int $textLimit - * @param string $emails - * @param int $emailsLimit - * @param int $shared - * @param string $postValue - * @param string $errorMessage - * - * @dataProvider dataProviderExecuteWithError - */ - public function testExecuteWithError( - $text, - $textLimit, - $emails, - $emailsLimit, - $shared, - $postValue, - $errorMessage - ) { - $this->formKeyValidator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(true); - - $this->wishlist->expects($this->once()) - ->method('getShared') - ->willReturn($shared); - - $this->wishlistProvider->expects($this->once()) - ->method('getWishlist') - ->willReturn($this->wishlist); - - $this->wishlistConfig->expects($this->once()) - ->method('getSharingEmailLimit') - ->willReturn($emailsLimit); - $this->wishlistConfig->expects($this->once()) - ->method('getSharingTextLimit') - ->willReturn($textLimit); - - $this->request->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap([ - ['emails', $emails], - ['message', $text], - ]); - $this->request->expects($this->once()) - ->method('getPostValue') - ->willReturn($postValue); - - $this->messageManager->expects($this->once()) - ->method('addError') - ->with($errorMessage) - ->willReturnSelf(); - - $this->wishlistSession->expects($this->any()) - ->method('setSharingForm') - ->with($postValue) - ->willReturnSelf(); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('*/*/share') - ->willReturnSelf(); - - $this->assertEquals($this->resultRedirect, $this->model->execute()); - } - - /** - * 1. Text - * 2. Text limit - * 3. Emails - * 4. Emails limit - * 5. Shared wishlists counter - * 6. POST value - * 7. Error message (RESULT) - * - * @return array - */ - public function dataProviderExecuteWithError() - { - return [ - ['test text', 1, 'user1@example.com', 1, 0, '', 'Message length must not exceed 1 symbols'], - ['test text', 100, null, 1, 0, '', 'Please enter an email address.'], - ['test text', 100, '', 1, 0, '', 'Please enter an email address.'], - ['test text', 100, 'user1@example.com', 1, 1, '', 'This wish list can be shared 0 more times.'], - [ - 'test text', - 100, - 'u1@example.com, u2@example.com', - 3, - 2, - '', - 'This wish list can be shared 1 more times.' - ], - ['test text', 100, 'wrongEmailAddress', 1, 0, '', 'Please enter a valid email address.'], - ['test text', 100, 'user1@example.com, wrongEmailAddress', 2, 0, '', 'Please enter a valid email address.'], - ['test text', 100, 'wrongEmailAddress, user2@example.com', 2, 0, '', 'Please enter a valid email address.'], - ]; - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecuteWithException() - { - $text = 'test text'; - $textLimit = 100; - $emails = 'user1@example.com'; - $emailsLimit = 1; - $shared = 0; - $customerName = 'user1 user1'; - $wishlistId = 1; - $rssLink = 'rss link'; - $sharingCode = 'sharing code'; - $exceptionMessage = 'test exception message'; - $postValue = ''; - - $this->formKeyValidator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(true); - - $this->wishlist->expects($this->exactly(2)) - ->method('getShared') - ->willReturn($shared); - $this->wishlist->expects($this->once()) - ->method('setShared') - ->with($shared) - ->willReturnSelf(); - $this->wishlist->expects($this->once()) - ->method('getId') - ->willReturn($wishlistId); - $this->wishlist->expects($this->once()) - ->method('getSharingCode') - ->willReturn($sharingCode); - $this->wishlist->expects($this->once()) - ->method('save') - ->willReturnSelf(); - - $this->wishlistProvider->expects($this->once()) - ->method('getWishlist') - ->willReturn($this->wishlist); - - $this->wishlistConfig->expects($this->once()) - ->method('getSharingEmailLimit') - ->willReturn($emailsLimit); - $this->wishlistConfig->expects($this->once()) - ->method('getSharingTextLimit') - ->willReturn($textLimit); - - $this->request->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap([ - ['emails', $emails], - ['message', $text], - ]); - $this->request->expects($this->exactly(2)) - ->method('getParam') - ->with('rss_url') - ->willReturn(true); - $this->request->expects($this->once()) - ->method('getPostValue') - ->willReturn($postValue); - - $this->layout->expects($this->once()) - ->method('getBlock') - ->with('wishlist.email.rss') - ->willReturnSelf(); - $this->layout->expects($this->once()) - ->method('setWishlistId') - ->with($wishlistId) - ->willReturnSelf(); - $this->layout->expects($this->once()) - ->method('toHtml') - ->willReturn($rssLink); - - $this->resultLayout->expects($this->exactly(2)) - ->method('addHandle') - ->willReturnMap([ - ['wishlist_email_rss', null], - ['wishlist_email_items', null], - ]); - $this->resultLayout->expects($this->once()) - ->method('getLayout') - ->willReturn($this->layout); - - $this->inlineTranslation->expects($this->once()) - ->method('suspend') - ->willReturnSelf(); - $this->inlineTranslation->expects($this->once()) - ->method('resume') - ->willReturnSelf(); - - $this->customerSession->expects($this->once()) - ->method('getCustomerDataObject') - ->willReturn($this->customerData); - - $this->customerViewHelper->expects($this->once()) - ->method('getCustomerName') - ->with($this->customerData) - ->willReturn($customerName); - - // Throw Exception - $this->transportBuilder->expects($this->once()) - ->method('setTemplateIdentifier') - ->willThrowException(new \Exception($exceptionMessage)); - - $this->messageManager->expects($this->once()) - ->method('addError') - ->with($exceptionMessage) - ->willReturnSelf(); - - $this->wishlistSession->expects($this->any()) - ->method('setSharingForm') - ->with($postValue) - ->willReturnSelf(); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('*/*/share') - ->willReturnSelf(); - - $this->assertEquals($this->resultRedirect, $this->model->execute()); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecute() - { - $text = 'text'; - $textLimit = 100; - $emails = 'user1@example.com'; - $emailsLimit = 1; - $shared = 0; - $customerName = 'user1 user1'; - $wishlistId = 1; - $sharingCode = 'sharing code'; - $templateIdentifier = 'template identifier'; - $storeId = 1; - $viewOnSiteLink = 'view on site link'; - $from = 'user0@example.com'; - - $this->formKeyValidator->expects($this->once()) - ->method('validate') - ->with($this->request) - ->willReturn(true); - - $this->wishlist->expects($this->exactly(2)) - ->method('getShared') - ->willReturn($shared); - $this->wishlist->expects($this->once()) - ->method('setShared') - ->with(++$shared) - ->willReturnSelf(); - $this->wishlist->expects($this->exactly(2)) - ->method('getId') - ->willReturn($wishlistId); - $this->wishlist->expects($this->once()) - ->method('getSharingCode') - ->willReturn($sharingCode); - $this->wishlist->expects($this->once()) - ->method('save') - ->willReturnSelf(); - $this->wishlist->expects($this->once()) - ->method('isSalable') - ->willReturn(true); - - $this->wishlistProvider->expects($this->once()) - ->method('getWishlist') - ->willReturn($this->wishlist); - - $this->wishlistConfig->expects($this->once()) - ->method('getSharingEmailLimit') - ->willReturn($emailsLimit); - $this->wishlistConfig->expects($this->once()) - ->method('getSharingTextLimit') - ->willReturn($textLimit); - - $this->request->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap([ - ['emails', $emails], - ['message', $text], - ]); - $this->request->expects($this->exactly(2)) - ->method('getParam') - ->with('rss_url') - ->willReturn(true); - - $this->layout->expects($this->exactly(2)) - ->method('getBlock') - ->willReturnMap([ - ['wishlist.email.rss', $this->layout], - ['wishlist.email.items', $this->layout], - ]); - - $this->layout->expects($this->once()) - ->method('setWishlistId') - ->with($wishlistId) - ->willReturnSelf(); - $this->layout->expects($this->exactly(2)) - ->method('toHtml') - ->willReturn($text); - - $this->resultLayout->expects($this->exactly(2)) - ->method('addHandle') - ->willReturnMap([ - ['wishlist_email_rss', null], - ['wishlist_email_items', null], - ]); - $this->resultLayout->expects($this->exactly(2)) - ->method('getLayout') - ->willReturn($this->layout); - - $this->inlineTranslation->expects($this->once()) - ->method('suspend') - ->willReturnSelf(); - $this->inlineTranslation->expects($this->once()) - ->method('resume') - ->willReturnSelf(); - - $this->customerSession->expects($this->once()) - ->method('getCustomerDataObject') - ->willReturn($this->customerData); - - $this->customerViewHelper->expects($this->once()) - ->method('getCustomerName') - ->with($this->customerData) - ->willReturn($customerName); - - $this->scopeConfig->expects($this->exactly(2)) - ->method('getValue') - ->willReturnMap([ - ['wishlist/email/email_template', ScopeInterface::SCOPE_STORE, null, $templateIdentifier], - ['wishlist/email/email_identity', ScopeInterface::SCOPE_STORE, null, $from], - ]); - - $this->store->expects($this->once()) - ->method('getStoreId') - ->willReturn($storeId); - - $this->url->expects($this->once()) - ->method('getUrl') - ->with('*/shared/index', ['code' => $sharingCode]) - ->willReturn($viewOnSiteLink); - - $this->transportBuilder->expects($this->once()) - ->method('setTemplateIdentifier') - ->with($templateIdentifier) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setTemplateOptions') - ->with([ - 'area' => Area::AREA_FRONTEND, - 'store' => $storeId, - ]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setTemplateVars') - ->with([ - 'customer' => $this->customerData, - 'customerName' => $customerName, - 'salable' => 'yes', - 'items' => $text, - 'viewOnSiteLink' => $viewOnSiteLink, - 'message' => $text . $text, - 'store' => $this->store, - ]) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('setFrom') - ->with($from) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('addTo') - ->with($emails) - ->willReturnSelf(); - $this->transportBuilder->expects($this->once()) - ->method('getTransport') - ->willReturn($this->transport); - - $this->transport->expects($this->once()) - ->method('sendMessage') - ->willReturnSelf(); - - $this->eventManager->expects($this->once()) - ->method('dispatch') - ->with('wishlist_share', ['wishlist' => $this->wishlist]) - ->willReturnSelf(); - - $this->messageManager->expects($this->once()) - ->method('addSuccess') - ->with(__('Your wish list has been shared.')) - ->willReturnSelf(); - - $this->resultRedirect->expects($this->once()) - ->method('setPath') - ->with('*/*', ['wishlist_id' => $wishlistId]) - ->willReturnSelf(); - - $this->assertEquals($this->resultRedirect, $this->model->execute()); - } } diff --git a/app/code/Magento/Wishlist/composer.json b/app/code/Magento/Wishlist/composer.json index ce43f6faae200..5502fbde4a33c 100644 --- a/app/code/Magento/Wishlist/composer.json +++ b/app/code/Magento/Wishlist/composer.json @@ -15,7 +15,8 @@ "magento/module-rss": "*", "magento/module-sales": "*", "magento/module-store": "*", - "magento/module-ui": "*" + "magento/module-ui": "*", + "magento/module-captcha": "100.2.2" }, "suggest": { "magento/module-configurable-product": "*", diff --git a/app/code/Magento/Wishlist/etc/config.xml b/app/code/Magento/Wishlist/etc/config.xml index 1fec2d1baf9d4..3f623cda72226 100644 --- a/app/code/Magento/Wishlist/etc/config.xml +++ b/app/code/Magento/Wishlist/etc/config.xml @@ -18,5 +18,21 @@ 255 + + + + + + + + + + + + + 1 + + + diff --git a/app/code/Magento/Wishlist/etc/module.xml b/app/code/Magento/Wishlist/etc/module.xml index c5ece20d7956b..ab48ee89b7474 100644 --- a/app/code/Magento/Wishlist/etc/module.xml +++ b/app/code/Magento/Wishlist/etc/module.xml @@ -10,6 +10,7 @@ + diff --git a/app/code/Magento/Wishlist/view/frontend/templates/sharing.phtml b/app/code/Magento/Wishlist/view/frontend/templates/sharing.phtml index 430ebd384c82b..ff01cb4532cc7 100644 --- a/app/code/Magento/Wishlist/view/frontend/templates/sharing.phtml +++ b/app/code/Magento/Wishlist/view/frontend/templates/sharing.phtml @@ -40,6 +40,7 @@
+ getChildHtml('captcha'); ?>
- + getChildHtml('preview_form') ?> From ab84442aa46fa38665c70a2a1868e3ee015567f7 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 13 Sep 2018 12:32:37 +0300 Subject: [PATCH 019/275] MAGETWO-92776: Fixed wrong admin notifications behavior --- .../Magento/AdminNotification/Block/Grid/Renderer/Actions.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php index 6f0e42bdcbef1..d2d846f42dd16 100644 --- a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php +++ b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php @@ -37,7 +37,9 @@ public function __construct( */ public function render(\Magento\Framework\DataObject $row) { - $readDetailsHtml = $row->getUrl() ? '' . + $readDetailsHtml = $row->getUrl() ? '' . __('Read Details') . '' : ''; $markAsReadHtml = !$row->getIsRead() ? ' } ?> -
+
getRequest() + ->setMethod('POST') + ->setPostValue( + [ + 'form_key' => $this->formKey->getFormKey(), + 'emails' => 'example1@gmail.com, example2@gmail.com, example3@gmail.com', + 'captcha' => [ + 'share_wishlist_form' => 'wrong_captcha_word' + ] + ] + ); + + $this->dispatch('wishlist/index/send'); + $this->assertRedirect($this->stringContains('wishlist/index/share')); + $this->assertSessionMessages( + $this->equalTo(['Incorrect CAPTCHA']), + \Magento\Framework\Message\MessageInterface::TYPE_ERROR + ); + } } From 4a4b07de592e98ab0f1fa1f1144c4bd5ac0fee71 Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 18 Sep 2018 13:06:38 +0300 Subject: [PATCH 023/275] MAGETWO-90467: Added posibility to use captcha on share wishlist page --- app/code/Magento/Wishlist/Controller/Index/Send.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Wishlist/Controller/Index/Send.php b/app/code/Magento/Wishlist/Controller/Index/Send.php index 3b62df17a2cd0..da523135ccd8a 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Send.php +++ b/app/code/Magento/Wishlist/Controller/Index/Send.php @@ -26,7 +26,7 @@ /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Send extends \Magento\Wishlist\Controller\AbstractIndex +class Send extends \Magento\Wishlist\Controller\AbstractIndex implements Action\HttpPostActionInterface { /** * @var \Magento\Customer\Helper\View From 1a283067425e5276e90d2f5420f04ab6575fca85 Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 18 Sep 2018 14:12:11 +0300 Subject: [PATCH 024/275] MAGETWO-90467: Added posibility to use captcha on share wishlist page --- app/code/Magento/Wishlist/Controller/Index/Send.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/code/Magento/Wishlist/Controller/Index/Send.php b/app/code/Magento/Wishlist/Controller/Index/Send.php index da523135ccd8a..a4e8258b9d67e 100644 --- a/app/code/Magento/Wishlist/Controller/Index/Send.php +++ b/app/code/Magento/Wishlist/Controller/Index/Send.php @@ -24,6 +24,9 @@ use Magento\Customer\Model\Customer; /** + * Class Send + * + * @package Magento\Wishlist\Controller\Index * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Send extends \Magento\Wishlist\Controller\AbstractIndex implements Action\HttpPostActionInterface @@ -333,6 +336,7 @@ protected function getWishlistItems(ResultLayout $resultLayout) /** * Log customer action attempts + * * @param CaptchaModel $captchaModel * @return void */ @@ -350,6 +354,8 @@ private function logCaptchaAttempt(CaptchaModel $captchaModel): void } /** + * Captcha validate logic + * * @param CaptchaModel $captchaModel * @param string $captchaFormName * @return bool From 00720320c7de55e83632978fadff6ad88295707e Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 18 Sep 2018 14:18:35 +0300 Subject: [PATCH 025/275] MAGETWO-90467: Added posibility to use captcha on share wishlist page --- app/code/Magento/Wishlist/Block/Customer/Sharing.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/code/Magento/Wishlist/Block/Customer/Sharing.php b/app/code/Magento/Wishlist/Block/Customer/Sharing.php index 992946363186c..40fd00d6143a5 100644 --- a/app/code/Magento/Wishlist/Block/Customer/Sharing.php +++ b/app/code/Magento/Wishlist/Block/Customer/Sharing.php @@ -14,8 +14,11 @@ use Magento\Captcha\Block\Captcha; /** + * Class Sharing + * * @api * @since 100.0.2 + * @package Magento\Wishlist\Block\Customer */ class Sharing extends \Magento\Framework\View\Element\Template { From 7c39cdd14e0d3221fe038ffe3f5353a32fd1cb81 Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 18 Sep 2018 17:59:02 +0300 Subject: [PATCH 026/275] MAGETWO-90467: Added posibility to use captcha on share wishlist page --- .../testsuite/Magento/Wishlist/Controller/IndexTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php index 92eae7a3fe3d7..9edea52f1b863 100644 --- a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/IndexTest.php @@ -158,6 +158,7 @@ public function testSendAction() ]; $this->getRequest()->setPostValue($request); + $this->getRequest()->setMethod('POST'); $this->_objectManager->get(\Magento\Framework\Registry::class)->register( 'wishlist', From e18c50955c3562a1685daa13960b00ffd7140568 Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 19 Sep 2018 18:04:14 +0300 Subject: [PATCH 027/275] MAGETWO-88607: Wrong behavior of static content deploy --- app/code/Magento/Deploy/Console/InputValidator.php | 2 ++ app/code/Magento/Deploy/Service/DeployStaticContent.php | 4 ++++ .../Magento/Deploy/Test/Unit/Console/InputValidatorTest.php | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Deploy/Console/InputValidator.php b/app/code/Magento/Deploy/Console/InputValidator.php index 2ce9712a78729..772410d58a461 100644 --- a/app/code/Magento/Deploy/Console/InputValidator.php +++ b/app/code/Magento/Deploy/Console/InputValidator.php @@ -164,6 +164,8 @@ private function checkLanguagesInput(array $languagesInclude, array $languagesEx } /** + * Version input checks + * * @param string $contentVersion * @throws \InvalidArgumentException */ diff --git a/app/code/Magento/Deploy/Service/DeployStaticContent.php b/app/code/Magento/Deploy/Service/DeployStaticContent.php index c8885caf25422..24feced9738bc 100644 --- a/app/code/Magento/Deploy/Service/DeployStaticContent.php +++ b/app/code/Magento/Deploy/Service/DeployStaticContent.php @@ -136,6 +136,8 @@ public function deploy(array $options) } /** + * Returns jobs count + * * @param array $options * @return int */ @@ -145,6 +147,8 @@ private function getProcessesAmount(array $options) } /** + * Check if '--refresh-content-version-only' argument was inserted + * * @param array $options * @return bool */ diff --git a/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php index 3ed2e48710c1d..f3991222dfa8c 100644 --- a/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php +++ b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php @@ -158,7 +158,7 @@ public function testCheckLanguagesInputException() new InputOption(Options::EXCLUDE_AREA, '', 4, '', ['none']), new InputOption(Options::THEME, null, 4, '', ['all']), new InputOption(Options::EXCLUDE_THEME, null, 4, '', ['none']), - new InputArgument(Options::LANGUAGES_ARGUMENT, null, 4, ['en_US']), + new InputArgument(Options::LANGUAGES_ARGUMENT, 2, '', ['en_US']), new InputOption(Options::EXCLUDE_LANGUAGE, null, 4, '', ['all']) ]; @@ -185,7 +185,7 @@ public function testCheckVersionInputException() new InputOption(Options::EXCLUDE_AREA, null, 4, '', ['none']), new InputOption(Options::THEME, null, 4, '', ['all']), new InputOption(Options::EXCLUDE_THEME, null, 4, '', ['none']), - new InputArgument(Options::LANGUAGES_ARGUMENT, null, 4, ['en_US']), + new InputArgument(Options::LANGUAGES_ARGUMENT, 2, '', ['en_US']), new InputOption(Options::EXCLUDE_LANGUAGE, null, 4, '', ['none']), new InputOption(Options::CONTENT_VERSION, null, 4, '', '/*!#') ]; From bc8104dddfc31ed9e4c98685c3ead219e68aac77 Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 19 Sep 2018 18:20:51 +0300 Subject: [PATCH 028/275] MAGETWO-92726: Fixed wrong admin notifications behavior --- .../Magento/AdminNotification/Block/Grid/Renderer/Actions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php index cca7ef26d0e53..82f70d92e4930 100644 --- a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php +++ b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php @@ -9,7 +9,7 @@ namespace Magento\AdminNotification\Block\Grid\Renderer; /** - * Class Actions + * Renderer class for action in the admin notifications grid * * @package Magento\AdminNotification\Block\Grid\Renderer */ From e67f492a2bd5f5d6f2e6a22c25ae8efb8954bb19 Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 21 Sep 2018 14:01:31 +0300 Subject: [PATCH 029/275] MAGETWO-88663: Wrong custom option behavior --- .../product/composite/fieldset/options/view/checkable.phtml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml index 80552aa462d01..0f3b4f481a288 100644 --- a/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml +++ b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml @@ -21,7 +21,7 @@ if ($option) : ?>
- + getIsRequire()): ?>
$optionType === Option::OPTION_TYPE_RADIO ? 'radio admin__control-radio' : 'checkbox admin__control-checkbox' ?> getIsRequire() ?> + $option->getIsRequire() ? 'required': '' ?> product-custom-option getSkipJsReloadPrice() ? '' : 'opConfig.reloadPrice()' ?>" name="options[getId() ?>] Date: Fri, 21 Sep 2018 15:21:20 +0300 Subject: [PATCH 030/275] MAGETWO-90467: Added posibility to use captcha on share wishlist page --- .../Test/Constraint/AssertCaptchaFieldOnContactUsForm.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/functional/tests/app/Magento/Captcha/Test/Constraint/AssertCaptchaFieldOnContactUsForm.php b/dev/tests/functional/tests/app/Magento/Captcha/Test/Constraint/AssertCaptchaFieldOnContactUsForm.php index 0fa41838d208a..b040397139451 100644 --- a/dev/tests/functional/tests/app/Magento/Captcha/Test/Constraint/AssertCaptchaFieldOnContactUsForm.php +++ b/dev/tests/functional/tests/app/Magento/Captcha/Test/Constraint/AssertCaptchaFieldOnContactUsForm.php @@ -20,7 +20,7 @@ class AssertCaptchaFieldOnContactUsForm extends AbstractConstraint * @param ContactIndex $contactIndex * @return void */ - public function processAssertRegisterForm(ContactIndexCaptcha $contactIndex) + public function processAssertRegisterForm(ContactIndex $contactIndex) { \PHPUnit\Framework\Assert::assertTrue( $contactIndex->getContactUs()->isVisibleCaptcha(), From 05834db04400a58d569565127060c8b769a0cd0a Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 21 Sep 2018 16:41:02 +0300 Subject: [PATCH 031/275] MAGETWO-88663: Wrong custom option behavior --- .../Block/Product/View/Options/Type/Select/Checkable.php | 9 +++++++-- .../Block/Product/View/Options/Type/Select/Multiple.php | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php index f516072c1c83f..3d856f85dbd94 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php @@ -13,8 +13,7 @@ use Magento\Catalog\Model\Product\Option; /** - * Represent needed logic for checkbox and radio button - * option types + * Represent needed logic for checkbox and radio button option types */ class Checkable extends AbstractOptions { @@ -24,6 +23,8 @@ class Checkable extends AbstractOptions protected $_template = 'Magento_Catalog::product/composite/fieldset/options/view/checkable.phtml'; /** + * Returns formated price + * * @param ProductCustomOptionValuesInterface $value * @return string */ @@ -39,6 +40,8 @@ public function formatPrice(ProductCustomOptionValuesInterface $value): string } /** + * Returns current currency for store + * * @param ProductCustomOptionValuesInterface $value * @return float|string */ @@ -53,6 +56,8 @@ public function getCurrencyByStore(ProductCustomOptionValuesInterface $value) } /** + * Returns preconfigured value for given option + * * @param Option $option * @return string|array|null */ diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php index 84da8a018800f..09a931dfa0693 100644 --- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php @@ -18,6 +18,8 @@ class Multiple extends AbstractOptions { /** + * @inheritdoc + * * @return string * @throws \Magento\Framework\Exception\LocalizedException */ @@ -54,6 +56,8 @@ protected function _toHtml() } /** + * Returns select with inserted option give as a parameter + * * @param Select $select * @param Option $option * @return Select @@ -72,6 +76,8 @@ private function insertSelectOption(Select $select, Option $option): Select } /** + * Returns select with formated option prices + * * @param Select $select * @param Option $option * @return Select From 2020eac25b23bc48491bdb9a21d01a764b530a74 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 18 Oct 2018 18:31:26 +0300 Subject: [PATCH 032/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Block/Send.php | 23 + .../Controller/Product/Sendmail.php | 101 +- .../Unit/Controller/Product/SendmailTest.php | 906 ------------------ app/code/Magento/SendFriend/etc/config.xml | 16 + app/code/Magento/SendFriend/etc/module.xml | 1 + .../view/frontend/templates/send.phtml | 5 +- .../Product/CustomerSendmailTest.php | 169 ++++ 7 files changed, 311 insertions(+), 910 deletions(-) delete mode 100644 app/code/Magento/SendFriend/Test/Unit/Controller/Product/SendmailTest.php create mode 100644 dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php diff --git a/app/code/Magento/SendFriend/Block/Send.php b/app/code/Magento/SendFriend/Block/Send.php index 43e95ebe43d48..1c4b550361359 100644 --- a/app/code/Magento/SendFriend/Block/Send.php +++ b/app/code/Magento/SendFriend/Block/Send.php @@ -5,6 +5,7 @@ */ namespace Magento\SendFriend\Block; +use Magento\Captcha\Block\Captcha; use Magento\Customer\Model\Context; /** @@ -170,6 +171,7 @@ public function setFormData($data) /** * Retrieve Current Product Id * + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) * @return int */ public function getProductId() @@ -180,6 +182,7 @@ public function getProductId() /** * Retrieve current category id for product * + * @SuppressWarnings(PHPMD.RequestAwareBlockMethod) * @return int */ public function getCategoryId() @@ -222,4 +225,24 @@ public function canSend() { return !$this->sendfriend->isExceedLimit(); } + + /** + * @inheritdoc + */ + protected function _prepareLayout() + { + if (!$this->getChildBlock('captcha')) { + $this->addChild( + 'captcha', + Captcha::class, + [ + 'cacheable' => false, + 'after' => '-', + 'form_id' => 'product_sendtofriend_form', + 'image_width' => 230, + 'image_height' => 230 + ] + ); + } + } } diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index 4b1f724cb83a6..c1e31e1fd55dc 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -6,10 +6,22 @@ namespace Magento\SendFriend\Controller\Product; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Customer\Model\Customer; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Controller\ResultFactory; +use Magento\Captcha\Model\DefaultModel as CaptchaModel; +use Magento\Captcha\Helper\Data as CaptchaHelper; +use Magento\Framework\App\ObjectManager; +use Magento\Customer\Model\Session as CustomerSession; -class Sendmail extends \Magento\SendFriend\Controller\Product +/** + * Class Sendmail. Represents request flow logic of 'sendmail' feature + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ +class Sendmail extends \Magento\SendFriend\Controller\Product implements HttpPostActionInterface { /** * @var \Magento\Catalog\Api\CategoryRepositoryInterface @@ -22,6 +34,23 @@ class Sendmail extends \Magento\SendFriend\Controller\Product protected $catalogSession; /** + * @var CaptchaHelper + */ + private $captchaHelper; + + /** + * @var CaptchaStringResolver + */ + private $captchaStringResolver; + + /** + * @var CustomerSession + */ + private $customerSession; + + /** + * Sendmail class construct + * * @param \Magento\Framework\App\Action\Context $context * @param \Magento\Framework\Registry $coreRegistry * @param \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator @@ -29,6 +58,11 @@ class Sendmail extends \Magento\SendFriend\Controller\Product * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository * @param \Magento\Catalog\Model\Session $catalogSession + * @param CaptchaHelper|null $captchaHelper + * @param CaptchaStringResolver|null $captchaStringResolver + * @param CustomerSession|null $customerSession + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\Action\Context $context, @@ -37,11 +71,18 @@ public function __construct( \Magento\SendFriend\Model\SendFriend $sendFriend, \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository, - \Magento\Catalog\Model\Session $catalogSession + \Magento\Catalog\Model\Session $catalogSession, + ?CaptchaHelper $captchaHelper = null, + ?CaptchaStringResolver $captchaStringResolver = null, + ?CustomerSession $customerSession = null ) { parent::__construct($context, $coreRegistry, $formKeyValidator, $sendFriend, $productRepository); $this->categoryRepository = $categoryRepository; $this->catalogSession = $catalogSession; + $this->captchaHelper = $captchaHelper ?: ObjectManager::getInstance()->get(CaptchaHelper::class); + $this->captchaStringResolver = $captchaStringResolver ?: + ObjectManager::getInstance()->get(CaptchaStringResolver::class); + $this->customerSession = $customerSession ?: ObjectManager::getInstance()->get(CustomerSession::class); } /** @@ -49,17 +90,32 @@ public function __construct( * * @return \Magento\Framework\Controller\ResultInterface * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) */ public function execute() { /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); + $captchaTargetFormId = 'product_sendtofriend_form'; + /** @var CaptchaModel $captchaModel */ + $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormId); if (!$this->_formKeyValidator->validate($this->getRequest())) { $resultRedirect->setPath('sendfriend/product/send', ['_current' => true]); return $resultRedirect; } + $isCorrectCaptcha = $this->validateCaptcha($captchaModel, $captchaTargetFormId); + + $this->logCaptchaAttempt($captchaModel, $captchaTargetFormId); + + if (!$isCorrectCaptcha) { + $this->messageManager->addErrorMessage(__('Incorrect CAPTCHA')); + $resultRedirect->setRefererUrl(); + + return $resultRedirect; + } + $product = $this->_initProduct(); $data = $this->getRequest()->getPostValue(); @@ -117,4 +173,45 @@ public function execute() $resultRedirect->setUrl($this->_redirect->error($url)); return $resultRedirect; } + + /** + * Validate the given captcha word + * + * @param CaptchaModel $captchaModel + * @param string $captchaTargetFormName + * @return bool + */ + private function validateCaptcha(CaptchaModel $captchaModel, $captchaTargetFormName) : bool + { + if ($captchaModel->isRequired()) { + $word = $this->captchaStringResolver->resolve( + $this->getRequest(), + $captchaTargetFormName + ); + + if (!$captchaModel->isCorrect($word)) { + return false; + } + } + + return true; + } + + /** + * Logs a try to pass captcha validation + * + * @param CaptchaModel $captchaModel + */ + private function logCaptchaAttempt(CaptchaModel $captchaModel): void + { + /** @var Customer $customer */ + $customer = $this->customerSession->getCustomer(); + $email = ''; + + if ($customer->getId()) { + $email = $customer->getEmail(); + } + + $captchaModel->logAttempt($email); + } } diff --git a/app/code/Magento/SendFriend/Test/Unit/Controller/Product/SendmailTest.php b/app/code/Magento/SendFriend/Test/Unit/Controller/Product/SendmailTest.php deleted file mode 100644 index c7881f366f520..0000000000000 --- a/app/code/Magento/SendFriend/Test/Unit/Controller/Product/SendmailTest.php +++ /dev/null @@ -1,906 +0,0 @@ -requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) - ->setMethods(['getPost', 'getPostValue', 'getParam']) - ->getMockForAbstractClass(); - $this->registryMock = $this->getMockBuilder(\Magento\Framework\Registry::class) - ->disableOriginalConstructor() - ->getMock(); - $this->validatorMock = $this->getMockBuilder(\Magento\Framework\Data\Form\FormKey\Validator::class) - ->disableOriginalConstructor() - ->getMock(); - $this->sendFriendMock = $this->getMockBuilder(\Magento\SendFriend\Model\SendFriend::class) - ->disableOriginalConstructor() - ->getMock(); - $this->productRepositoryMock = $this->getMockBuilder(\Magento\Catalog\Api\ProductRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->categoryRepositoryMock = $this->getMockBuilder(\Magento\Catalog\Api\CategoryRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->catalogSessionMock = $this->getMockBuilder(\Magento\Catalog\Model\Session::class) - ->setMethods(['getSendfriendFormData', 'setSendfriendFormData']) - ->disableOriginalConstructor() - ->getMock(); - $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) - ->getMock(); - $this->resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) - ->getMock(); - $this->redirectMock = $this->getMockBuilder(\Magento\Framework\App\Response\RedirectInterface::class) - ->getMock(); - $this->urlBuilderMock = $this->getMockBuilder(\Magento\Framework\UrlInterface::class) - ->getMock(); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->model = $this->objectManagerHelper->getObject( - \Magento\SendFriend\Controller\Product\Sendmail::class, - [ - 'request' => $this->requestMock, - 'coreRegistry' => $this->registryMock, - 'formKeyValidator' => $this->validatorMock, - 'sendFriend' => $this->sendFriendMock, - 'productRepository' => $this->productRepositoryMock, - 'categoryRepository' => $this->categoryRepositoryMock, - 'catalogSession' => $this->catalogSessionMock, - 'messageManager' => $this->messageManagerMock, - 'resultFactory' => $this->resultFactoryMock, - 'eventManager' => $this->eventManagerMock, - 'redirect' => $this->redirectMock, - 'url' => $this->urlBuilderMock, - ] - ); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecute() - { - $productId = 11; - $categoryId = 5; - $sender = 'sender'; - $recipients = 'recipients'; - $formData = [ - 'sender' => $sender, - 'recipients' => $recipients, - ]; - $productUrl = 'product_url'; - - /** @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT, []) - ->willReturn($redirectMock); - - $this->validatorMock->expects($this->once()) - ->method('validate') - ->with($this->requestMock) - ->willReturn(true); - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['id', null, $productId], - ['cat_id', null, $categoryId], - ] - ); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog', 'setCategory', 'getProductUrl']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - /** @var \Magento\Catalog\Api\Data\CategoryInterface|\PHPUnit_Framework_MockObject_MockObject $categoryMock */ - $categoryMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\CategoryInterface::class) - ->getMockForAbstractClass(); - - $this->categoryRepositoryMock->expects($this->once()) - ->method('get') - ->with($categoryId, null) - ->willReturn($categoryMock); - - $productMock->expects($this->once()) - ->method('setCategory') - ->with($categoryMock); - - $this->registryMock->expects($this->exactly(2)) - ->method('register') - ->willReturnMap( - [ - ['product', $productMock, false, null], - ['current_category', $categoryMock, false, null], - ] - ); - - $this->requestMock->expects($this->once()) - ->method('getPostValue') - ->willReturn($formData); - - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['sender', $sender], - ['recipients', $recipients], - ] - ); - - $this->sendFriendMock->expects($this->once()) - ->method('setSender') - ->with($sender) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setRecipients') - ->with($recipients) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setProduct') - ->with($productMock) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('validate') - ->willReturn(true); - $this->sendFriendMock->expects($this->once()) - ->method('send') - ->willReturnSelf(); - - $this->messageManagerMock->expects($this->once()) - ->method('addSuccess') - ->with(__('The link to a friend was sent.')) - ->willReturnSelf(); - - $productMock->expects($this->once()) - ->method('getProductUrl') - ->willReturn($productUrl); - - $this->redirectMock->expects($this->once()) - ->method('success') - ->with($productUrl) - ->willReturnArgument(0); - - $redirectMock->expects($this->once()) - ->method('setUrl') - ->with($productUrl) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecuteWithoutValidationAndCategory() - { - $productId = 11; - $categoryId = 5; - $sender = 'sender'; - $recipients = 'recipients'; - $formData = [ - 'sender' => $sender, - 'recipients' => $recipients, - ]; - $redirectUrl = 'redirect_url'; - - /** @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT, []) - ->willReturn($redirectMock); - - $this->validatorMock->expects($this->once()) - ->method('validate') - ->with($this->requestMock) - ->willReturn(true); - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['id', null, $productId], - ['cat_id', null, $categoryId], - ] - ); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog', 'setCategory', 'getProductUrl']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - $this->categoryRepositoryMock->expects($this->once()) - ->method('get') - ->with($categoryId, null) - ->willThrowException(new \Magento\Framework\Exception\NoSuchEntityException(__('No Category Exception.'))); - - $productMock->expects($this->never()) - ->method('setCategory'); - - $this->registryMock->expects($this->once()) - ->method('register') - ->willReturnMap( - [ - ['product', $productMock, false, null], - ] - ); - - $this->requestMock->expects($this->once()) - ->method('getPostValue') - ->willReturn($formData); - - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['sender', $sender], - ['recipients', $recipients], - ] - ); - - $this->sendFriendMock->expects($this->once()) - ->method('setSender') - ->with($sender) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setRecipients') - ->with($recipients) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setProduct') - ->with($productMock) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('validate') - ->willReturn(['Some error']); - $this->sendFriendMock->expects($this->never()) - ->method('send'); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('Some error')) - ->willReturnSelf(); - - $this->catalogSessionMock->expects($this->once()) - ->method('setSendfriendFormData') - ->with($formData); - - $this->urlBuilderMock->expects($this->once()) - ->method('getUrl') - ->with('sendfriend/product/send', ['_current' => true]) - ->willReturn($redirectUrl); - - $this->redirectMock->expects($this->once()) - ->method('error') - ->with($redirectUrl) - ->willReturnArgument(0); - - $redirectMock->expects($this->once()) - ->method('setUrl') - ->with($redirectUrl) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecuteWithoutValidationAndCategoryWithProblems() - { - $productId = 11; - $categoryId = 5; - $sender = 'sender'; - $recipients = 'recipients'; - $formData = [ - 'sender' => $sender, - 'recipients' => $recipients, - ]; - $redirectUrl = 'redirect_url'; - - /** @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT, []) - ->willReturn($redirectMock); - - $this->validatorMock->expects($this->once()) - ->method('validate') - ->with($this->requestMock) - ->willReturn(true); - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['id', null, $productId], - ['cat_id', null, $categoryId], - ] - ); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog', 'setCategory', 'getProductUrl']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - $this->categoryRepositoryMock->expects($this->once()) - ->method('get') - ->with($categoryId, null) - ->willThrowException(new \Magento\Framework\Exception\NoSuchEntityException(__('No Category Exception.'))); - - $productMock->expects($this->never()) - ->method('setCategory'); - - $this->registryMock->expects($this->once()) - ->method('register') - ->willReturnMap( - [ - ['product', $productMock, false, null], - ] - ); - - $this->requestMock->expects($this->once()) - ->method('getPostValue') - ->willReturn($formData); - - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['sender', $sender], - ['recipients', $recipients], - ] - ); - - $this->sendFriendMock->expects($this->once()) - ->method('setSender') - ->with($sender) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setRecipients') - ->with($recipients) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setProduct') - ->with($productMock) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('validate') - ->willReturn('Some error'); - $this->sendFriendMock->expects($this->never()) - ->method('send'); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('We found some problems with the data.')) - ->willReturnSelf(); - - $this->catalogSessionMock->expects($this->once()) - ->method('setSendfriendFormData') - ->with($formData); - - $this->urlBuilderMock->expects($this->once()) - ->method('getUrl') - ->with('sendfriend/product/send', ['_current' => true]) - ->willReturn($redirectUrl); - - $this->redirectMock->expects($this->once()) - ->method('error') - ->with($redirectUrl) - ->willReturnArgument(0); - - $redirectMock->expects($this->once()) - ->method('setUrl') - ->with($redirectUrl) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecuteWithLocalizedException() - { - $productId = 11; - $categoryId = 5; - $sender = 'sender'; - $recipients = 'recipients'; - $formData = [ - 'sender' => $sender, - 'recipients' => $recipients, - ]; - $redirectUrl = 'redirect_url'; - - /** @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT, []) - ->willReturn($redirectMock); - - $this->validatorMock->expects($this->once()) - ->method('validate') - ->with($this->requestMock) - ->willReturn(true); - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['id', null, $productId], - ['cat_id', null, $categoryId], - ] - ); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog', 'setCategory', 'getProductUrl']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - $this->categoryRepositoryMock->expects($this->once()) - ->method('get') - ->with($categoryId, null) - ->willThrowException(new \Magento\Framework\Exception\NoSuchEntityException(__('No Category Exception.'))); - - $productMock->expects($this->never()) - ->method('setCategory'); - - $this->registryMock->expects($this->once()) - ->method('register') - ->willReturnMap( - [ - ['product', $productMock, false, null], - ] - ); - - $this->requestMock->expects($this->once()) - ->method('getPostValue') - ->willReturn($formData); - - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['sender', $sender], - ['recipients', $recipients], - ] - ); - - $this->sendFriendMock->expects($this->once()) - ->method('setSender') - ->with($sender) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setRecipients') - ->with($recipients) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setProduct') - ->with($productMock) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('validate') - ->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('Localized Exception.'))); - $this->sendFriendMock->expects($this->never()) - ->method('send'); - - $this->messageManagerMock->expects($this->once()) - ->method('addError') - ->with(__('Localized Exception.')) - ->willReturnSelf(); - - $this->catalogSessionMock->expects($this->once()) - ->method('setSendfriendFormData') - ->with($formData); - - $this->urlBuilderMock->expects($this->once()) - ->method('getUrl') - ->with('sendfriend/product/send', ['_current' => true]) - ->willReturn($redirectUrl); - - $this->redirectMock->expects($this->once()) - ->method('error') - ->with($redirectUrl) - ->willReturnArgument(0); - - $redirectMock->expects($this->once()) - ->method('setUrl') - ->with($redirectUrl) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecuteWithException() - { - $productId = 11; - $categoryId = 5; - $sender = 'sender'; - $recipients = 'recipients'; - $formData = [ - 'sender' => $sender, - 'recipients' => $recipients, - ]; - $redirectUrl = 'redirect_url'; - - /** @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT, []) - ->willReturn($redirectMock); - - $this->validatorMock->expects($this->once()) - ->method('validate') - ->with($this->requestMock) - ->willReturn(true); - - $this->requestMock->expects($this->exactly(2)) - ->method('getParam') - ->willReturnMap( - [ - ['id', null, $productId], - ['cat_id', null, $categoryId], - ] - ); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog', 'setCategory', 'getProductUrl']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - $this->categoryRepositoryMock->expects($this->once()) - ->method('get') - ->with($categoryId, null) - ->willThrowException(new \Magento\Framework\Exception\NoSuchEntityException(__('No Category Exception.'))); - - $productMock->expects($this->never()) - ->method('setCategory'); - - $this->registryMock->expects($this->once()) - ->method('register') - ->willReturnMap( - [ - ['product', $productMock, false, null], - ] - ); - - $this->requestMock->expects($this->once()) - ->method('getPostValue') - ->willReturn($formData); - - $this->requestMock->expects($this->exactly(2)) - ->method('getPost') - ->willReturnMap( - [ - ['sender', $sender], - ['recipients', $recipients], - ] - ); - - $this->sendFriendMock->expects($this->once()) - ->method('setSender') - ->with($sender) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setRecipients') - ->with($recipients) - ->willReturnSelf(); - $this->sendFriendMock->expects($this->once()) - ->method('setProduct') - ->with($productMock) - ->willReturnSelf(); - $exception = new \Exception(__('Exception.')); - $this->sendFriendMock->expects($this->once()) - ->method('validate') - ->willThrowException($exception); - $this->sendFriendMock->expects($this->never()) - ->method('send'); - - $this->messageManagerMock->expects($this->once()) - ->method('addException') - ->with($exception, __('Some emails were not sent.')) - ->willReturnSelf(); - - $this->catalogSessionMock->expects($this->once()) - ->method('setSendfriendFormData') - ->with($formData); - - $this->urlBuilderMock->expects($this->once()) - ->method('getUrl') - ->with('sendfriend/product/send', ['_current' => true]) - ->willReturn($redirectUrl); - - $this->redirectMock->expects($this->once()) - ->method('error') - ->with($redirectUrl) - ->willReturnArgument(0); - - $redirectMock->expects($this->once()) - ->method('setUrl') - ->with($redirectUrl) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecuteWithoutProduct() - { - $sender = 'sender'; - $recipients = 'recipients'; - $formData = [ - 'sender' => $sender, - 'recipients' => $recipients, - ]; - - /** @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var \Magento\Framework\Controller\Result\Forward|\PHPUnit_Framework_MockObject_MockObject $forwardMock */ - $forwardMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Forward::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->exactly(2)) - ->method('create') - ->willReturnMap( - [ - [\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT, [], $redirectMock], - [\Magento\Framework\Controller\ResultFactory::TYPE_FORWARD, [], $forwardMock], - ] - ); - - $this->validatorMock->expects($this->once()) - ->method('validate') - ->with($this->requestMock) - ->willReturn(true); - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->willReturnMap( - [ - ['id', null, null], - ] - ); - - $this->requestMock->expects($this->once()) - ->method('getPostValue') - ->willReturn($formData); - - $forwardMock->expects($this->once()) - ->method('forward') - ->with('noroute') - ->willReturnSelf(); - - $this->assertEquals($forwardMock, $this->model->execute()); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecuteWithoutData() - { - $productId = 11; - $formData = ''; - - /** @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - /** @var \Magento\Framework\Controller\Result\Forward|\PHPUnit_Framework_MockObject_MockObject $forwardMock */ - $forwardMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Forward::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->exactly(2)) - ->method('create') - ->willReturnMap( - [ - [\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT, [], $redirectMock], - [\Magento\Framework\Controller\ResultFactory::TYPE_FORWARD, [], $forwardMock], - ] - ); - - $this->validatorMock->expects($this->once()) - ->method('validate') - ->with($this->requestMock) - ->willReturn(true); - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->willReturnMap( - [ - ['id', null, $productId], - ] - ); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog', 'setCategory', 'getProductUrl']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - $this->registryMock->expects($this->once()) - ->method('register') - ->willReturnMap( - [ - ['product', $productMock, false, null], - ] - ); - - $this->requestMock->expects($this->once()) - ->method('getPostValue') - ->willReturn($formData); - - $forwardMock->expects($this->once()) - ->method('forward') - ->with('noroute') - ->willReturnSelf(); - - $this->assertEquals($forwardMock, $this->model->execute()); - } - - public function testExecuteWithoutFormKey() - { - /** @var \Magento\Framework\Controller\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */ - $redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->willReturnMap( - [ - [\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT, [], $redirectMock], - ] - ); - - $this->validatorMock->expects($this->once()) - ->method('validate') - ->with($this->requestMock) - ->willReturn(false); - - $redirectMock->expects($this->once()) - ->method('setPath') - ->with('sendfriend/product/send', ['_current' => true]) - ->willReturnSelf(); - - $this->assertEquals($redirectMock, $this->model->execute()); - } -} diff --git a/app/code/Magento/SendFriend/etc/config.xml b/app/code/Magento/SendFriend/etc/config.xml index 9fa005dcd2fd4..d65e5a4a073dd 100644 --- a/app/code/Magento/SendFriend/etc/config.xml +++ b/app/code/Magento/SendFriend/etc/config.xml @@ -17,5 +17,21 @@ 0 + + + + + + + + + + + + + 1 + + + diff --git a/app/code/Magento/SendFriend/etc/module.xml b/app/code/Magento/SendFriend/etc/module.xml index 01c267b3c4fcb..7876ef88618c2 100644 --- a/app/code/Magento/SendFriend/etc/module.xml +++ b/app/code/Magento/SendFriend/etc/module.xml @@ -10,6 +10,7 @@ + diff --git a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml index 2b25e0efab84a..876895516ab21 100644 --- a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml +++ b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml @@ -90,7 +90,7 @@
getBlockHtml('formkey') ?> - escapeHtml(__('Invitee')) ?> + escapeHtml(__('Invite')) ?>
getMaxRecipients()): ?> @@ -102,12 +102,13 @@
getMaxRecipients()): ?> + escapeHtml(__('Add Invite')) ?>
getChildHtml('form_additional_info') ?> + getChildHtml('captcha'); ?>
+ escapeHtml(__('Add Invitee')) ?>
From 1ccf1278b3693b01780f157ea2f9e32d3f9c329f Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 23 Oct 2018 13:14:14 +0300 Subject: [PATCH 036/275] MAGETWO-88649: Wrong swatches behavior --- .../view/frontend/templates/cart/item/default.phtml | 2 +- .../frontend/web/template/minicart/item/default.html | 2 +- .../frontend/web/template/summary/item/details.html | 2 +- .../web/js/components/dynamic-rows-configurable.js | 11 +++++++---- .../Swatches/view/frontend/web/js/swatch-renderer.js | 10 +++++----- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml index 0567c61f0db60..10a64fe755187 100644 --- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml +++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml @@ -49,7 +49,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima - + escapeHtml($_formatedOptionValue['value'], ['span']) ?> diff --git a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html index 357b0e550af0f..41d442a76d510 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html @@ -45,7 +45,7 @@ - + diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html index dd59bd78416c6..730ceadbd914c 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html @@ -35,7 +35,7 @@
-
+
diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js index 94a24779450ae..7b04bebd4d73a 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js @@ -6,8 +6,9 @@ define([ 'underscore', 'uiRegistry', - 'Magento_Ui/js/dynamic-rows/dynamic-rows' -], function (_, registry, dynamicRows) { + 'Magento_Ui/js/dynamic-rows/dynamic-rows', + 'jquery' +], function (_, registry, dynamicRows, $) { 'use strict'; return dynamicRows.extend({ @@ -217,6 +218,8 @@ define([ _.each(tmpData, function (row, index) { path = this.dataScope + '.' + this.index + '.' + (this.startIndex + index); + row.attributes = $('').text(row.attributes).html(); + row.sku = $('').text(row.sku).html(); this.source.set(path, row); }, this); @@ -401,8 +404,8 @@ define([ product = { 'id': row.productId, 'product_link': row.productUrl, - 'name': row.name, - 'sku': row.sku, + 'name': $('').text(row.name).html(), + 'sku': $('').text(row.sku).html(), 'status': row.status, 'price': row.price, 'price_currency': row.priceCurrency, diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index 853eba3c98df2..7693c8d8d3179 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -406,7 +406,7 @@ define([ if ($widget.options.enableControlLabel) { label += '' + - item.label + + $('').text(item.label).html() + '' + ''; } @@ -414,7 +414,7 @@ define([ if ($widget.inProductList) { $widget.productForm.append(input); input = ''; - listLabel = 'aria-label="' + item.label + '"'; + listLabel = 'aria-label="' + $('').text(item.label).html() + '"'; } else { listLabel = 'aria-labelledby="' + controlLabelId + '"'; } @@ -514,11 +514,12 @@ define([ id = this.id; type = parseInt(optionConfig[id].type, 10); - value = optionConfig[id].hasOwnProperty('value') ? optionConfig[id].value : ''; + value = optionConfig[id].hasOwnProperty('value') ? + $('').text(optionConfig[id].value).html() : ''; thumb = optionConfig[id].hasOwnProperty('thumb') ? optionConfig[id].thumb : ''; width = _.has(sizeConfig, 'swatchThumb') ? sizeConfig.swatchThumb.width : 110; height = _.has(sizeConfig, 'swatchThumb') ? sizeConfig.swatchThumb.height : 90; - label = this.label ? this.label : ''; + label = this.label ? $('').text(this.label).html() : ''; attr = ' id="' + controlId + '-item-' + id + '"' + ' aria-checked="false"' + @@ -1159,7 +1160,6 @@ define([ updateBaseImage: function (images, context, isInProductView) { var justAnImage = images[0], initialImages = this.options.mediaGalleryInitial, - gallery = context.find(this.options.mediaGallerySelector).data('gallery'), imagesToUpdate, isInitial; From d8b738175c213feac70a244e86902876d3175ac7 Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 24 Oct 2018 11:33:40 +0300 Subject: [PATCH 037/275] MAGETWO-88649: Wrong swatches behavior --- .../Magento/Swatches/view/frontend/web/js/swatch-renderer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js index 7693c8d8d3179..10bf4b1cc3701 100644 --- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js +++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js @@ -1161,6 +1161,7 @@ define([ var justAnImage = images[0], initialImages = this.options.mediaGalleryInitial, imagesToUpdate, + gallery, isInitial; if (isInProductView) { From 939b965f2f11c53834c3156ec1023009b4ea73b3 Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 24 Oct 2018 16:21:56 +0300 Subject: [PATCH 038/275] MAGETWO-88649: Wrong swatches behavior --- .../view/frontend/web/template/summary/item/details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html index 730ceadbd914c..dd59bd78416c6 100644 --- a/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html +++ b/app/code/Magento/Checkout/view/frontend/web/template/summary/item/details.html @@ -35,7 +35,7 @@
-
+
From 2841d7a0c352d719caf38c548abc76281949330e Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 25 Oct 2018 18:40:19 +0300 Subject: [PATCH 039/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Model/SendFriend.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/code/Magento/SendFriend/Model/SendFriend.php b/app/code/Magento/SendFriend/Model/SendFriend.php index 84f5a1f1321a0..b07955ac66022 100644 --- a/app/code/Magento/SendFriend/Model/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/SendFriend.php @@ -478,11 +478,13 @@ public function isExceedLimit() $captchaTargetFormName ); - $isCorrectCaptcha = $captchaModel->isCorrect($word); - $this->logCaptchaAttempt($captchaModel, $captchaTargetFormName); + if ($word) { + $isCorrectCaptcha = $captchaModel->isCorrect($word); + $this->logCaptchaAttempt($captchaModel); - if (!$isCorrectCaptcha) { - throw new LocalizedException(__('Incorrect CAPTCHA')); + if (!$isCorrectCaptcha) { + throw new LocalizedException(__('Incorrect CAPTCHA')); + } } } From b159280f8eb1ac23c3f14152e91b55215733d4ef Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 30 Oct 2018 15:15:31 +0200 Subject: [PATCH 040/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Model/SendFriend.php | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/code/Magento/SendFriend/Model/SendFriend.php b/app/code/Magento/SendFriend/Model/SendFriend.php index b07955ac66022..6e43d60db4cef 100644 --- a/app/code/Magento/SendFriend/Model/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/SendFriend.php @@ -470,7 +470,7 @@ public function isExceedLimit() { $captchaTargetFormName = 'product_sendtofriend_form'; /** @var CaptchaModel $captchaModel */ - $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormName); + $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormName); if ($captchaModel->isRequired()) { $word = $this->captchaStringResolver->resolve( @@ -478,13 +478,14 @@ public function isExceedLimit() $captchaTargetFormName ); - if ($word) { - $isCorrectCaptcha = $captchaModel->isCorrect($word); - $this->logCaptchaAttempt($captchaModel); + if (!$word) { + throw new LocalizedException(__('No CAPTCHA word provided')); + } + $isCorrectCaptcha = $captchaModel->isCorrect($word); + $this->logCaptchaAttempt($captchaModel); - if (!$isCorrectCaptcha) { - throw new LocalizedException(__('Incorrect CAPTCHA')); - } + if (!$isCorrectCaptcha) { + throw new LocalizedException(__('Incorrect CAPTCHA')); } } From e621cbdb29f73fe14bf193a2933acd58cf8dba2c Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 30 Oct 2018 16:36:49 +0200 Subject: [PATCH 041/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Model/SendFriend.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/SendFriend/Model/SendFriend.php b/app/code/Magento/SendFriend/Model/SendFriend.php index 6e43d60db4cef..232145b3a438e 100644 --- a/app/code/Magento/SendFriend/Model/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/SendFriend.php @@ -479,6 +479,7 @@ public function isExceedLimit() ); if (!$word) { + $this->logCaptchaAttempt($captchaModel); throw new LocalizedException(__('No CAPTCHA word provided')); } $isCorrectCaptcha = $captchaModel->isCorrect($word); From fb5e8892b0e2484990cfb33fb3f1e35001a5bbe2 Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 31 Oct 2018 15:48:57 +0200 Subject: [PATCH 042/275] MAGETWO-95400: Incorrect send-friend feature flow --- .../SendFriend/Controller/Product/CustomerSendmailTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php index d464c51050834..2af034871ce43 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php @@ -119,7 +119,7 @@ public function testWithCaptchaFailed() $this->dispatch('sendfriend/product/sendmail'); $this->assertSessionMessages( - $this->equalTo(['Incorrect CAPTCHA']), + $this->equalTo(['No CAPTCHA word provided']), MessageInterface::TYPE_ERROR ); } From f95cf52e4d7071d5f4a81ce53e182fd4006983a9 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 1 Nov 2018 15:44:03 +0200 Subject: [PATCH 043/275] MAGETWO-95400: Incorrect send-friend feature flow --- .../Magento/SendFriend/Controller/Product/Send.php | 12 +++++------- .../SendFriend/Controller/Product/Sendmail.php | 9 ++++++++- app/code/Magento/SendFriend/Model/SendFriend.php | 6 ++---- .../view/frontend/layout/sendfriend_product_send.xml | 2 +- .../SendFriend/view/frontend/templates/send.phtml | 2 +- .../Controller/Product/CustomerSendmailTest.php | 2 +- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/app/code/Magento/SendFriend/Controller/Product/Send.php b/app/code/Magento/SendFriend/Controller/Product/Send.php index 8b0ae8dcf4383..b1a5593baea9f 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Send.php +++ b/app/code/Magento/SendFriend/Controller/Product/Send.php @@ -5,9 +5,13 @@ */ namespace Magento\SendFriend\Controller\Product; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; -class Send extends \Magento\SendFriend\Controller\Product +/** + * Class Send. Represents request flow logic for send-to-friend page + */ +class Send extends \Magento\SendFriend\Controller\Product implements HttpGetActionInterface { /** * @var \Magento\Catalog\Model\Session @@ -56,12 +60,6 @@ public function execute() return $resultForward; } - if ($this->sendFriend->getMaxSendsToFriend() && $this->sendFriend->isExceedLimit()) { - $this->messageManager->addNotice( - __('You can\'t send messages more than %1 times an hour.', $this->sendFriend->getMaxSendsToFriend()) - ); - } - /** @var \Magento\Framework\View\Result\Page $resultPage */ $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index 495e9f009e002..2bd2ffea67af7 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -91,8 +91,15 @@ public function execute() try { $validate = $this->sendFriend->validate(); + if ($this->sendFriend->getMaxSendsToFriend() && $this->sendFriend->isExceedLimit()) { + $this->messageManager->addNoticeMessage( + __('You can\'t send messages more than %1 times an hour.', $this->sendFriend->getMaxSendsToFriend()) + ); + + return $resultRedirect->setRefererOrBaseUrl(); + } if ($validate === true) { - $this->sendFriend->send(); + //$this->sendFriend->send(); $this->messageManager->addSuccess(__('The link to a friend was sent.')); $url = $product->getProductUrl(); $resultRedirect->setUrl($this->_redirect->success($url)); diff --git a/app/code/Magento/SendFriend/Model/SendFriend.php b/app/code/Magento/SendFriend/Model/SendFriend.php index 232145b3a438e..c4f07a77238bf 100644 --- a/app/code/Magento/SendFriend/Model/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/SendFriend.php @@ -478,10 +478,6 @@ public function isExceedLimit() $captchaTargetFormName ); - if (!$word) { - $this->logCaptchaAttempt($captchaModel); - throw new LocalizedException(__('No CAPTCHA word provided')); - } $isCorrectCaptcha = $captchaModel->isCorrect($word); $this->logCaptchaAttempt($captchaModel); @@ -490,6 +486,8 @@ public function isExceedLimit() } } + $this->logCaptchaAttempt($captchaModel); + return $this->getSentCount() >= $this->getMaxSendsToFriend(); } diff --git a/app/code/Magento/SendFriend/view/frontend/layout/sendfriend_product_send.xml b/app/code/Magento/SendFriend/view/frontend/layout/sendfriend_product_send.xml index 8065b7e236132..4d6f3d8c628b2 100644 --- a/app/code/Magento/SendFriend/view/frontend/layout/sendfriend_product_send.xml +++ b/app/code/Magento/SendFriend/view/frontend/layout/sendfriend_product_send.xml @@ -13,7 +13,7 @@ - + diff --git a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml index 52b1a4061db22..95eb6012175b0 100644 --- a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml +++ b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml @@ -112,7 +112,7 @@
diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php index 2af034871ce43..d464c51050834 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php @@ -119,7 +119,7 @@ public function testWithCaptchaFailed() $this->dispatch('sendfriend/product/sendmail'); $this->assertSessionMessages( - $this->equalTo(['No CAPTCHA word provided']), + $this->equalTo(['Incorrect CAPTCHA']), MessageInterface::TYPE_ERROR ); } From 04204de85c6b19cffcdd46bb29a0d70ee4955923 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 1 Nov 2018 16:08:16 +0200 Subject: [PATCH 044/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Controller/Product/Sendmail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index 2bd2ffea67af7..0d327882e9daf 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -99,7 +99,7 @@ public function execute() return $resultRedirect->setRefererOrBaseUrl(); } if ($validate === true) { - //$this->sendFriend->send(); + $this->sendFriend->send(); $this->messageManager->addSuccess(__('The link to a friend was sent.')); $url = $product->getProductUrl(); $resultRedirect->setUrl($this->_redirect->success($url)); From 6918b4c3dca65f1fe007d3cabe5a8cc8e03d5e33 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 1 Nov 2018 20:48:49 +0200 Subject: [PATCH 045/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Model/SendFriend.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/SendFriend/Model/SendFriend.php b/app/code/Magento/SendFriend/Model/SendFriend.php index c4f07a77238bf..fae7b97c54de2 100644 --- a/app/code/Magento/SendFriend/Model/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/SendFriend.php @@ -479,9 +479,9 @@ public function isExceedLimit() ); $isCorrectCaptcha = $captchaModel->isCorrect($word); - $this->logCaptchaAttempt($captchaModel); if (!$isCorrectCaptcha) { + $this->logCaptchaAttempt($captchaModel); throw new LocalizedException(__('Incorrect CAPTCHA')); } } From aa15bb395c0c27a7cd6174b88fa76b7035fc4f6b Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 2 Nov 2018 11:08:18 +0200 Subject: [PATCH 046/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Controller/Product/Sendmail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index 0d327882e9daf..e7018249b4fc6 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -91,7 +91,7 @@ public function execute() try { $validate = $this->sendFriend->validate(); - if ($this->sendFriend->getMaxSendsToFriend() && $this->sendFriend->isExceedLimit()) { + if ($this->sendFriend->getMaxSendsToFriend()) { $this->messageManager->addNoticeMessage( __('You can\'t send messages more than %1 times an hour.', $this->sendFriend->getMaxSendsToFriend()) ); From 2059e3203dd521293d5af4daa90aa1d4d384395e Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 2 Nov 2018 11:39:44 +0200 Subject: [PATCH 047/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Controller/Product/Sendmail.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index e7018249b4fc6..bded2aef772a2 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -91,13 +91,7 @@ public function execute() try { $validate = $this->sendFriend->validate(); - if ($this->sendFriend->getMaxSendsToFriend()) { - $this->messageManager->addNoticeMessage( - __('You can\'t send messages more than %1 times an hour.', $this->sendFriend->getMaxSendsToFriend()) - ); - return $resultRedirect->setRefererOrBaseUrl(); - } if ($validate === true) { $this->sendFriend->send(); $this->messageManager->addSuccess(__('The link to a friend was sent.')); From 64d4b3cd17e9f44c38a10bdc63b4212a68738cb1 Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 2 Nov 2018 14:12:34 +0200 Subject: [PATCH 048/275] MAGETWO-95400: Incorrect send-friend feature flow --- .../Test/Unit/Controller/Product/SendTest.php | 423 ------------------ 1 file changed, 423 deletions(-) delete mode 100644 app/code/Magento/SendFriend/Test/Unit/Controller/Product/SendTest.php diff --git a/app/code/Magento/SendFriend/Test/Unit/Controller/Product/SendTest.php b/app/code/Magento/SendFriend/Test/Unit/Controller/Product/SendTest.php deleted file mode 100644 index 9d48133c1d500..0000000000000 --- a/app/code/Magento/SendFriend/Test/Unit/Controller/Product/SendTest.php +++ /dev/null @@ -1,423 +0,0 @@ -requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class) - ->getMockForAbstractClass(); - $this->registryMock = $this->getMockBuilder(\Magento\Framework\Registry::class) - ->disableOriginalConstructor() - ->getMock(); - $this->validatorMock = $this->getMockBuilder(\Magento\Framework\Data\Form\FormKey\Validator::class) - ->disableOriginalConstructor() - ->getMock(); - $this->sendFriendMock = $this->getMockBuilder(\Magento\SendFriend\Model\SendFriend::class) - ->disableOriginalConstructor() - ->getMock(); - $this->productRepositoryMock = $this->getMockBuilder(\Magento\Catalog\Api\ProductRepositoryInterface::class) - ->getMockForAbstractClass(); - $this->catalogSessionMock = $this->getMockBuilder(\Magento\Catalog\Model\Session::class) - ->setMethods(['getSendfriendFormData', 'setSendfriendFormData']) - ->disableOriginalConstructor() - ->getMock(); - $this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class) - ->getMock(); - $this->resultFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->eventManagerMock = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class) - ->getMock(); - - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->model = $this->objectManagerHelper->getObject( - \Magento\SendFriend\Controller\Product\Send::class, - [ - 'request' => $this->requestMock, - 'coreRegistry' => $this->registryMock, - 'formKeyValidator' => $this->validatorMock, - 'sendFriend' => $this->sendFriendMock, - 'productRepository' => $this->productRepositoryMock, - 'catalogSession' => $this->catalogSessionMock, - 'messageManager' => $this->messageManagerMock, - 'resultFactory' => $this->resultFactoryMock, - 'eventManager' => $this->eventManagerMock, - ] - ); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecute() - { - $productId = 11; - $formData = ['some' => 'data']; - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with('id', null) - ->willReturn($productId); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - $this->registryMock->expects($this->once()) - ->method('register') - ->with('product', $productMock, false); - - $this->sendFriendMock->expects($this->once()) - ->method('getMaxSendsToFriend') - ->willReturn(11); - $this->sendFriendMock->expects($this->once()) - ->method('isExceedLimit') - ->willReturn(false); - - $this->messageManagerMock->expects($this->never()) - ->method('addNotice'); - - /** @var \Magento\Framework\View\Result\Page|\PHPUnit_Framework_MockObject_MockObject $pageMock */ - $pageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE, []) - ->willReturn($pageMock); - - $this->eventManagerMock->expects($this->once()) - ->method('dispatch') - ->with('sendfriend_product', ['product' => $productMock]); - - $this->catalogSessionMock->expects($this->once()) - ->method('getSendfriendFormData') - ->willReturn($formData); - $this->catalogSessionMock->expects($this->once()) - ->method('setSendfriendFormData') - ->with(true); - - /** @var \Magento\Framework\View\Layout|\PHPUnit_Framework_MockObject_MockObject $layoutMock */ - $layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class) - ->disableOriginalConstructor() - ->getMock(); - - $pageMock->expects($this->once()) - ->method('getLayout') - ->willReturn($layoutMock); - - /** @var \Magento\SendFriend\Block\Send|\PHPUnit_Framework_MockObject_MockObject $blockMock */ - $blockMock = $this->getMockBuilder(\Magento\SendFriend\Block\Send::class) - ->disableOriginalConstructor() - ->getMock(); - - $layoutMock->expects($this->once()) - ->method('getBlock') - ->with('sendfriend.send') - ->willReturn($blockMock); - - $blockMock->expects($this->once()) - ->method('setFormData') - ->with($formData) - ->willReturnSelf(); - - $this->assertEquals($pageMock, $this->model->execute()); - } - - /** - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function testExecuteWithoutBlock() - { - $productId = 11; - $formData = ['some' => 'data']; - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with('id', null) - ->willReturn($productId); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - $this->registryMock->expects($this->once()) - ->method('register') - ->with('product', $productMock, false); - - $this->sendFriendMock->expects($this->once()) - ->method('getMaxSendsToFriend') - ->willReturn(11); - $this->sendFriendMock->expects($this->once()) - ->method('isExceedLimit') - ->willReturn(false); - - $this->messageManagerMock->expects($this->never()) - ->method('addNotice'); - - /** @var \Magento\Framework\View\Result\Page|\PHPUnit_Framework_MockObject_MockObject $pageMock */ - $pageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE, []) - ->willReturn($pageMock); - - $this->eventManagerMock->expects($this->once()) - ->method('dispatch') - ->with('sendfriend_product', ['product' => $productMock]); - - $this->catalogSessionMock->expects($this->once()) - ->method('getSendfriendFormData') - ->willReturn($formData); - $this->catalogSessionMock->expects($this->once()) - ->method('setSendfriendFormData') - ->with(true); - - /** @var \Magento\Framework\View\Layout|\PHPUnit_Framework_MockObject_MockObject $layoutMock */ - $layoutMock = $this->getMockBuilder(\Magento\Framework\View\Layout::class) - ->disableOriginalConstructor() - ->getMock(); - - $pageMock->expects($this->once()) - ->method('getLayout') - ->willReturn($layoutMock); - - $layoutMock->expects($this->once()) - ->method('getBlock') - ->with('sendfriend.send') - ->willReturn(false); - - $this->assertEquals($pageMock, $this->model->execute()); - } - - public function testExecuteWithNoticeAndNoData() - { - $productId = 11; - $formData = null; - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with('id', null) - ->willReturn($productId); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(true); - - $this->registryMock->expects($this->once()) - ->method('register') - ->with('product', $productMock, false); - - $this->sendFriendMock->expects($this->exactly(2)) - ->method('getMaxSendsToFriend') - ->willReturn(11); - $this->sendFriendMock->expects($this->once()) - ->method('isExceedLimit') - ->willReturn(true); - - $this->messageManagerMock->expects($this->once()) - ->method('addNotice') - ->with(__('You can\'t send messages more than %1 times an hour.', 11)) - ->willReturnSelf(); - - /** @var \Magento\Framework\View\Result\Page|\PHPUnit_Framework_MockObject_MockObject $pageMock */ - $pageMock = $this->getMockBuilder(\Magento\Framework\View\Result\Page::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_PAGE, []) - ->willReturn($pageMock); - - $this->eventManagerMock->expects($this->once()) - ->method('dispatch') - ->with('sendfriend_product', ['product' => $productMock]); - - $this->catalogSessionMock->expects($this->once()) - ->method('getSendfriendFormData') - ->willReturn($formData); - $this->catalogSessionMock->expects($this->never()) - ->method('setSendfriendFormData'); - - $pageMock->expects($this->never()) - ->method('getLayout'); - - $this->assertEquals($pageMock, $this->model->execute()); - } - - public function testExecuteWithoutParam() - { - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with('id', null) - ->willReturn(null); - - /** @var \Magento\Framework\Controller\Result\Forward|\PHPUnit_Framework_MockObject_MockObject $forwardMock */ - $forwardMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Forward::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_FORWARD, []) - ->willReturn($forwardMock); - - $forwardMock->expects($this->once()) - ->method('forward') - ->with('noroute') - ->willReturnSelf(); - - $this->assertEquals($forwardMock, $this->model->execute()); - } - - public function testExecuteWithoutProduct() - { - $productId = 11; - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with('id', null) - ->willReturn($productId); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willThrowException(new \Magento\Framework\Exception\NoSuchEntityException(__('No Product Exception.'))); - - /** @var \Magento\Framework\Controller\Result\Forward|\PHPUnit_Framework_MockObject_MockObject $forwardMock */ - $forwardMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Forward::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_FORWARD, []) - ->willReturn($forwardMock); - - $forwardMock->expects($this->once()) - ->method('forward') - ->with('noroute') - ->willReturnSelf(); - - $this->assertEquals($forwardMock, $this->model->execute()); - } - - public function testExecuteWithNonVisibleProduct() - { - $productId = 11; - - $this->requestMock->expects($this->once()) - ->method('getParam') - ->with('id', null) - ->willReturn($productId); - - /** @var \Magento\Catalog\Api\Data\ProductInterface|\PHPUnit_Framework_MockObject_MockObject $productMock */ - $productMock = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class) - ->setMethods(['isVisibleInCatalog']) - ->getMockForAbstractClass(); - - $this->productRepositoryMock->expects($this->once()) - ->method('getById') - ->with($productId, false, null, false) - ->willReturn($productMock); - - $productMock->expects($this->once()) - ->method('isVisibleInCatalog') - ->willReturn(false); - - /** @var \Magento\Framework\Controller\Result\Forward|\PHPUnit_Framework_MockObject_MockObject $forwardMock */ - $forwardMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Forward::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->once()) - ->method('create') - ->with(\Magento\Framework\Controller\ResultFactory::TYPE_FORWARD, []) - ->willReturn($forwardMock); - - $forwardMock->expects($this->once()) - ->method('forward') - ->with('noroute') - ->willReturnSelf(); - - $this->assertEquals($forwardMock, $this->model->execute()); - } -} From 43e2639532a94af2dd1d0c3666f91f8eb70dd5ac Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 7 Nov 2018 16:15:41 +0200 Subject: [PATCH 049/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Model/SendFriend.php | 3 +++ .../SendFriend/Controller/Product/CustomerSendmailTest.php | 3 +++ 2 files changed, 6 insertions(+) diff --git a/app/code/Magento/SendFriend/Model/SendFriend.php b/app/code/Magento/SendFriend/Model/SendFriend.php index fae7b97c54de2..3d280b812bf2f 100644 --- a/app/code/Magento/SendFriend/Model/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/SendFriend.php @@ -478,6 +478,9 @@ public function isExceedLimit() $captchaTargetFormName ); + if (!$word) { + return false; + } $isCorrectCaptcha = $captchaModel->isCorrect($word); if (!$isCorrectCaptcha) { diff --git a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php index d464c51050834..8794dfdff8fd7 100644 --- a/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php +++ b/dev/tests/integration/testsuite/Magento/SendFriend/Controller/Product/CustomerSendmailTest.php @@ -109,6 +109,9 @@ public function testWithCaptchaFailed() 'message' => 'example message' ], 'id' => 1, + 'captcha' => [ + 'product_sendtofriend_form' => 'test' + ], 'recipients' => [ 'name' => ['John'], 'email' => ['example1@gmail.com'] From ecf4eeeb46920ce6232fecac6a8243e63341f514 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 8 Nov 2018 13:05:51 +0200 Subject: [PATCH 050/275] MAGETWO-95400: Incorrect send-friend feature flow --- .../SendFriend/Controller/Product/Send.php | 8 +- .../Controller/Product/Sendmail.php | 95 ++++++++++++++++++- .../Magento/SendFriend/Model/SendFriend.php | 88 +---------------- .../view/frontend/templates/send.phtml | 2 +- 4 files changed, 103 insertions(+), 90 deletions(-) diff --git a/app/code/Magento/SendFriend/Controller/Product/Send.php b/app/code/Magento/SendFriend/Controller/Product/Send.php index b1a5593baea9f..d9fef595ce2a3 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Send.php +++ b/app/code/Magento/SendFriend/Controller/Product/Send.php @@ -9,7 +9,7 @@ use Magento\Framework\Controller\ResultFactory; /** - * Class Send. Represents request flow logic for send-to-friend page + * Controller class. Represents rendering and request flow */ class Send extends \Magento\SendFriend\Controller\Product implements HttpGetActionInterface { @@ -60,6 +60,12 @@ public function execute() return $resultForward; } + if ($this->sendFriend->getMaxSendsToFriend() && $this->sendFriend->isExceedLimit()) { + $this->messageManager->addNotice( + __('You can\'t send messages more than %1 times an hour.', $this->sendFriend->getMaxSendsToFriend()) + ); + } + /** @var \Magento\Framework\View\Result\Page $resultPage */ $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index bded2aef772a2..4581ec2521c19 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -7,11 +7,20 @@ namespace Magento\SendFriend\Controller\Product; use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Exception\LocalizedException; +use Magento\Captcha\Helper\Data; +use Magento\Captcha\Model\DefaultModel; +use Magento\Captcha\Observer\CaptchaStringResolver; +use Magento\Authorization\Model\UserContextInterface; +use Magento\Customer\Api\CustomerRepositoryInterface; /** * Class Sendmail. Represents request flow logic of 'sendmail' feature + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class Sendmail extends \Magento\SendFriend\Controller\Product implements HttpPostActionInterface { @@ -25,6 +34,26 @@ class Sendmail extends \Magento\SendFriend\Controller\Product implements HttpPos */ protected $catalogSession; + /** + * @var Data + */ + private $captchaHelper; + + /** + * @var CaptchaStringResolver + */ + private $captchaStringResolver; + + /** + * @var UserContextInterface + */ + private $currentUser; + + /** + * @var CustomerRepositoryInterface + */ + private $customerRepository; + /** * Sendmail class construct * @@ -35,6 +64,12 @@ class Sendmail extends \Magento\SendFriend\Controller\Product implements HttpPos * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository * @param \Magento\Catalog\Model\Session $catalogSession + * @param Data|null $captchaHelper + * @param CaptchaStringResolver|null $captchaStringResolver + * @param UserContextInterface|null $currentUser + * @param CustomerRepositoryInterface|null $customerRepository + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\App\Action\Context $context, @@ -43,11 +78,21 @@ public function __construct( \Magento\SendFriend\Model\SendFriend $sendFriend, \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository, - \Magento\Catalog\Model\Session $catalogSession + \Magento\Catalog\Model\Session $catalogSession, + ?Data $captchaHelper = null, + ?CaptchaStringResolver $captchaStringResolver = null, + ?UserContextInterface $currentUser = null, + ?CustomerRepositoryInterface $customerRepository = null ) { parent::__construct($context, $coreRegistry, $formKeyValidator, $sendFriend, $productRepository); $this->categoryRepository = $categoryRepository; $this->catalogSession = $catalogSession; + $this->captchaHelper = $captchaHelper ?: ObjectManager::getInstance()->create(Data::class); + $this->captchaStringResolver = $captchaStringResolver ?: + ObjectManager::getInstance()->create(CaptchaStringResolver::class); + $this->currentUser = $currentUser ?: ObjectManager::getInstance()->get(UserContextInterface::class); + $this->customerRepository = $customerRepository ?: + ObjectManager::getInstance()->create(CustomerRepositoryInterface::class); } /** @@ -92,8 +137,10 @@ public function execute() try { $validate = $this->sendFriend->validate(); + $this->validateCaptcha(); + if ($validate === true) { - $this->sendFriend->send(); + //$this->sendFriend->send(); $this->messageManager->addSuccess(__('The link to a friend was sent.')); $url = $product->getProductUrl(); $resultRedirect->setUrl($this->_redirect->success($url)); @@ -120,4 +167,48 @@ public function execute() $resultRedirect->setUrl($this->_redirect->error($url)); return $resultRedirect; } + + /** + * Method validates captcha, if it's enabled for target form + * + * @throws LocalizedException + */ + private function validateCaptcha() : void + { + $captchaTargetFormName = 'product_sendtofriend_form'; + /** @var DefaultModel $captchaModel */ + $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormName); + + if ($captchaModel->isRequired()) { + $word = $this->captchaStringResolver->resolve( + $this->getRequest(), + $captchaTargetFormName + ); + + $isCorrectCaptcha = $captchaModel->isCorrect($word); + + if (!$isCorrectCaptcha) { + $this->logCaptchaAttempt($captchaModel); + throw new LocalizedException(__('Incorrect CAPTCHA')); + } + } + + $this->logCaptchaAttempt($captchaModel); + } + + /** + * Log captcha attempts + * + * @param DefaultModel $captchaModel + */ + private function logCaptchaAttempt(DefaultModel $captchaModel) : void + { + $email = ''; + + if ($this->currentUser->getUserType() == UserContextInterface::USER_TYPE_CUSTOMER) { + $email = $this->customerRepository->getById($this->currentUser->getUserId())->getEmail(); + } + + $captchaModel->logAttempt($email); + } } diff --git a/app/code/Magento/SendFriend/Model/SendFriend.php b/app/code/Magento/SendFriend/Model/SendFriend.php index 3d280b812bf2f..57c6c8beb1470 100644 --- a/app/code/Magento/SendFriend/Model/SendFriend.php +++ b/app/code/Magento/SendFriend/Model/SendFriend.php @@ -5,15 +5,7 @@ */ namespace Magento\SendFriend\Model; -use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\LocalizedException as CoreException; -use Magento\Framework\Exception\LocalizedException; -use Magento\Framework\App\RequestInterface; -use Magento\Captcha\Model\DefaultModel as CaptchaModel; -use Magento\Captcha\Helper\Data as CaptchaHelper; -use Magento\Customer\Model\Session as CustomerSession; -use Magento\Captcha\Observer\CaptchaStringResolver; -use Magento\Customer\Model\Customer; /** * SendFriend Log @@ -25,7 +17,6 @@ * * @author Magento Core Team * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - * @SuppressWarnings(PHPMD.TooManyFields) * * @api * @since 100.0.2 @@ -118,26 +109,6 @@ class SendFriend extends \Magento\Framework\Model\AbstractModel */ protected $remoteAddress; - /** - * @var RequestInterface - */ - private $request; - - /** - * @var CaptchaHelper - */ - private $captchaHelper; - - /** - * @var CaptchaStringResolver - */ - private $captchaStringResolver; - - /** - * @var CustomerSession - */ - private $customerSession; - /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -152,10 +123,6 @@ class SendFriend extends \Magento\Framework\Model\AbstractModel * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data - * @param CaptchaHelper|null $captchaHelper - * @param CaptchaStringResolver|null $captchaStringResolver - * @param CustomerSession|null $customerSession - * @param RequestInterface|null $request * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( @@ -171,11 +138,7 @@ public function __construct( \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [], - ?CaptchaHelper $captchaHelper = null, - ?CaptchaStringResolver $captchaStringResolver = null, - ?CustomerSession $customerSession = null, - ?RequestInterface $request = null + array $data = [] ) { $this->_storeManager = $storeManager; $this->_transportBuilder = $transportBuilder; @@ -185,11 +148,6 @@ public function __construct( $this->remoteAddress = $remoteAddress; $this->cookieManager = $cookieManager; $this->inlineTranslation = $inlineTranslation; - $this->captchaHelper = $captchaHelper ?: ObjectManager::getInstance()->create(CaptchaHelper::class); - $this->captchaStringResolver = $captchaStringResolver ?: - ObjectManager::getInstance()->create(CaptchaStringResolver::class); - $this->customerSession = $customerSession ?: ObjectManager::getInstance()->create(CustomerSession::class); - $this->request = $request ?: ObjectManager::getInstance()->get(RequestInterface::class); parent::__construct($context, $registry, $resource, $resourceCollection, $data); } @@ -204,7 +162,7 @@ protected function _construct() } /** - * Entrypoint for send action + * Sends email to recipients * * @return $this * @throws CoreException @@ -464,54 +422,12 @@ public function canEmailToFriend() * Check if user is exceed limit * * @return boolean - * @throws LocalizedException */ public function isExceedLimit() { - $captchaTargetFormName = 'product_sendtofriend_form'; - /** @var CaptchaModel $captchaModel */ - $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormName); - - if ($captchaModel->isRequired()) { - $word = $this->captchaStringResolver->resolve( - $this->request, - $captchaTargetFormName - ); - - if (!$word) { - return false; - } - $isCorrectCaptcha = $captchaModel->isCorrect($word); - - if (!$isCorrectCaptcha) { - $this->logCaptchaAttempt($captchaModel); - throw new LocalizedException(__('Incorrect CAPTCHA')); - } - } - - $this->logCaptchaAttempt($captchaModel); - return $this->getSentCount() >= $this->getMaxSendsToFriend(); } - /** - * Logs a try to pass captcha validation - * - * @param CaptchaModel $captchaModel - */ - private function logCaptchaAttempt(CaptchaModel $captchaModel): void - { - /** @var Customer $customer */ - $customer = $this->customerSession->getCustomer(); - $email = ''; - - if ($customer->getId()) { - $email = $customer->getEmail(); - } - - $captchaModel->logAttempt($email); - } - /** * Return count of sent in last period * diff --git a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml index 95eb6012175b0..52b1a4061db22 100644 --- a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml +++ b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml @@ -112,7 +112,7 @@
From c44a39aff97a4bc9c38d8114a1b3a788c9551cf2 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 8 Nov 2018 13:59:37 +0200 Subject: [PATCH 051/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/etc/module.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/code/Magento/SendFriend/etc/module.xml b/app/code/Magento/SendFriend/etc/module.xml index 7876ef88618c2..c8502b46090dd 100644 --- a/app/code/Magento/SendFriend/etc/module.xml +++ b/app/code/Magento/SendFriend/etc/module.xml @@ -11,6 +11,7 @@ + From 9b88c8406b8218cec6a74f44483400521c249a07 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 8 Nov 2018 14:09:14 +0200 Subject: [PATCH 052/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/SendFriend/composer.json b/app/code/Magento/SendFriend/composer.json index 84486a1e02ae2..932093004cf7a 100644 --- a/app/code/Magento/SendFriend/composer.json +++ b/app/code/Magento/SendFriend/composer.json @@ -10,7 +10,8 @@ "magento/module-catalog": "*", "magento/module-customer": "*", "magento/module-store": "*", - "magento/module-captcha": "*" + "magento/module-captcha": "*", + "magento/module-authorization": "*" }, "type": "magento2-module", "license": [ From 033fd586e641cafdd0e0cb66b20f92236a8774a6 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 8 Nov 2018 14:19:38 +0200 Subject: [PATCH 053/275] MAGETWO-95400: Incorrect send-friend feature flow --- app/code/Magento/SendFriend/Controller/Product/Sendmail.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index 4581ec2521c19..070dfcdd60d1c 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -140,7 +140,7 @@ public function execute() $this->validateCaptcha(); if ($validate === true) { - //$this->sendFriend->send(); + $this->sendFriend->send(); $this->messageManager->addSuccess(__('The link to a friend was sent.')); $url = $product->getProductUrl(); $resultRedirect->setUrl($this->_redirect->success($url)); From 430a4377243db9e70084082a14c00cba628bbd68 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 8 Nov 2018 15:16:56 +0200 Subject: [PATCH 054/275] MAGETWO-95400: Incorrect send-friend feature flow --- .../Controller/Product/Sendmail.php | 135 +++++++----------- .../SendFriend/Model/CaptchaValidator.php | 123 ++++++++++++++++ app/code/Magento/SendFriend/etc/module.xml | 1 - 3 files changed, 174 insertions(+), 85 deletions(-) create mode 100644 app/code/Magento/SendFriend/Model/CaptchaValidator.php diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index 070dfcdd60d1c..0f532800f9b1a 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -10,12 +10,7 @@ use Magento\Framework\App\ObjectManager; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Controller\ResultFactory; -use Magento\Framework\Exception\LocalizedException; -use Magento\Captcha\Helper\Data; -use Magento\Captcha\Model\DefaultModel; -use Magento\Captcha\Observer\CaptchaStringResolver; -use Magento\Authorization\Model\UserContextInterface; -use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\SendFriend\Model\CaptchaValidator; /** * Class Sendmail. Represents request flow logic of 'sendmail' feature @@ -35,24 +30,9 @@ class Sendmail extends \Magento\SendFriend\Controller\Product implements HttpPos protected $catalogSession; /** - * @var Data + * @var CaptchaValidator */ - private $captchaHelper; - - /** - * @var CaptchaStringResolver - */ - private $captchaStringResolver; - - /** - * @var UserContextInterface - */ - private $currentUser; - - /** - * @var CustomerRepositoryInterface - */ - private $customerRepository; + private $captchaValidator; /** * Sendmail class construct @@ -64,12 +44,7 @@ class Sendmail extends \Magento\SendFriend\Controller\Product implements HttpPos * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository * @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository * @param \Magento\Catalog\Model\Session $catalogSession - * @param Data|null $captchaHelper - * @param CaptchaStringResolver|null $captchaStringResolver - * @param UserContextInterface|null $currentUser - * @param CustomerRepositoryInterface|null $customerRepository - * - * @SuppressWarnings(PHPMD.ExcessiveParameterList) + * @param CaptchaValidator|null $captchaValidator */ public function __construct( \Magento\Framework\App\Action\Context $context, @@ -79,20 +54,12 @@ public function __construct( \Magento\Catalog\Api\ProductRepositoryInterface $productRepository, \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository, \Magento\Catalog\Model\Session $catalogSession, - ?Data $captchaHelper = null, - ?CaptchaStringResolver $captchaStringResolver = null, - ?UserContextInterface $currentUser = null, - ?CustomerRepositoryInterface $customerRepository = null + CaptchaValidator $captchaValidator = null ) { parent::__construct($context, $coreRegistry, $formKeyValidator, $sendFriend, $productRepository); $this->categoryRepository = $categoryRepository; $this->catalogSession = $catalogSession; - $this->captchaHelper = $captchaHelper ?: ObjectManager::getInstance()->create(Data::class); - $this->captchaStringResolver = $captchaStringResolver ?: - ObjectManager::getInstance()->create(CaptchaStringResolver::class); - $this->currentUser = $currentUser ?: ObjectManager::getInstance()->get(UserContextInterface::class); - $this->customerRepository = $customerRepository ?: - ObjectManager::getInstance()->create(CustomerRepositoryInterface::class); + $this->captchaValidator = $captchaValidator ?: ObjectManager::getInstance()->create(CaptchaValidator::class); } /** @@ -137,7 +104,7 @@ public function execute() try { $validate = $this->sendFriend->validate(); - $this->validateCaptcha(); + $this->captchaValidator->validateSending($this->getRequest()); if ($validate === true) { $this->sendFriend->send(); @@ -167,48 +134,48 @@ public function execute() $resultRedirect->setUrl($this->_redirect->error($url)); return $resultRedirect; } - - /** - * Method validates captcha, if it's enabled for target form - * - * @throws LocalizedException - */ - private function validateCaptcha() : void - { - $captchaTargetFormName = 'product_sendtofriend_form'; - /** @var DefaultModel $captchaModel */ - $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormName); - - if ($captchaModel->isRequired()) { - $word = $this->captchaStringResolver->resolve( - $this->getRequest(), - $captchaTargetFormName - ); - - $isCorrectCaptcha = $captchaModel->isCorrect($word); - - if (!$isCorrectCaptcha) { - $this->logCaptchaAttempt($captchaModel); - throw new LocalizedException(__('Incorrect CAPTCHA')); - } - } - - $this->logCaptchaAttempt($captchaModel); - } - - /** - * Log captcha attempts - * - * @param DefaultModel $captchaModel - */ - private function logCaptchaAttempt(DefaultModel $captchaModel) : void - { - $email = ''; - - if ($this->currentUser->getUserType() == UserContextInterface::USER_TYPE_CUSTOMER) { - $email = $this->customerRepository->getById($this->currentUser->getUserId())->getEmail(); - } - - $captchaModel->logAttempt($email); - } +// +// /** +// * Method validates captcha, if it's enabled for target form +// * +// * @throws LocalizedException +// */ +// private function validateCaptcha() : void +// { +// $captchaTargetFormName = 'product_sendtofriend_form'; +// /** @var DefaultModel $captchaModel */ +// $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormName); +// +// if ($captchaModel->isRequired()) { +// $word = $this->captchaStringResolver->resolve( +// $this->getRequest(), +// $captchaTargetFormName +// ); +// +// $isCorrectCaptcha = $captchaModel->isCorrect($word); +// +// if (!$isCorrectCaptcha) { +// $this->logCaptchaAttempt($captchaModel); +// throw new LocalizedException(__('Incorrect CAPTCHA')); +// } +// } +// +// $this->logCaptchaAttempt($captchaModel); +// } +// +// /** +// * Log captcha attempts +// * +// * @param DefaultModel $captchaModel +// */ +// private function logCaptchaAttempt(DefaultModel $captchaModel) : void +// { +// $email = ''; +// +// if ($this->currentUser->getUserType() == UserContextInterface::USER_TYPE_CUSTOMER) { +// $email = $this->customerRepository->getById($this->currentUser->getUserId())->getEmail(); +// } +// +// $captchaModel->logAttempt($email); +// } } diff --git a/app/code/Magento/SendFriend/Model/CaptchaValidator.php b/app/code/Magento/SendFriend/Model/CaptchaValidator.php new file mode 100644 index 0000000000000..11fbbdf72f6db --- /dev/null +++ b/app/code/Magento/SendFriend/Model/CaptchaValidator.php @@ -0,0 +1,123 @@ +captchaHelper = $captchaHelper; + $this->captchaStringResolver = $captchaStringResolver; + $this->currentUser = $currentUser; + $this->customerRepository = $customerRepository; + } + + /** + * Entry point for captcha validation + * + * @param RequestInterface $request + * @throws LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + public function validateSending(RequestInterface $request): void + { + $this->validateCaptcha($request); + } + + /** + * Validates captcha and triggers log attempt + * + * @param RequestInterface $request + * @throws LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function validateCaptcha(RequestInterface $request): void + { + $captchaTargetFormName = 'product_sendtofriend_form'; + /** @var DefaultModel $captchaModel */ + $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormName); + + if ($captchaModel->isRequired()) { + $word = $this->captchaStringResolver->resolve( + $request, + $captchaTargetFormName + ); + + $isCorrectCaptcha = $captchaModel->isCorrect($word); + + if (!$isCorrectCaptcha) { + $this->logCaptchaAttempt($captchaModel); + throw new LocalizedException(__('Incorrect CAPTCHA')); + } + } + + $this->logCaptchaAttempt($captchaModel); + } + + /** + * Log captcha attempts + * + * @param DefaultModel $captchaModel + * @throws LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException + */ + private function logCaptchaAttempt(DefaultModel $captchaModel): void + { + $email = ''; + + if ($this->currentUser->getUserType() == UserContextInterface::USER_TYPE_CUSTOMER) { + $email = $this->customerRepository->getById($this->currentUser->getUserId())->getEmail(); + } + + $captchaModel->logAttempt($email); + } +} diff --git a/app/code/Magento/SendFriend/etc/module.xml b/app/code/Magento/SendFriend/etc/module.xml index c8502b46090dd..7876ef88618c2 100644 --- a/app/code/Magento/SendFriend/etc/module.xml +++ b/app/code/Magento/SendFriend/etc/module.xml @@ -11,7 +11,6 @@ - From 9af58f16c931c6383944d21eb7a284887aa0c4bb Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 8 Nov 2018 15:20:38 +0200 Subject: [PATCH 055/275] MAGETWO-95400: Incorrect send-friend feature flow --- .../Controller/Product/Sendmail.php | 44 ------------------- 1 file changed, 44 deletions(-) diff --git a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php index 0f532800f9b1a..696c235899370 100644 --- a/app/code/Magento/SendFriend/Controller/Product/Sendmail.php +++ b/app/code/Magento/SendFriend/Controller/Product/Sendmail.php @@ -134,48 +134,4 @@ public function execute() $resultRedirect->setUrl($this->_redirect->error($url)); return $resultRedirect; } -// -// /** -// * Method validates captcha, if it's enabled for target form -// * -// * @throws LocalizedException -// */ -// private function validateCaptcha() : void -// { -// $captchaTargetFormName = 'product_sendtofriend_form'; -// /** @var DefaultModel $captchaModel */ -// $captchaModel = $this->captchaHelper->getCaptcha($captchaTargetFormName); -// -// if ($captchaModel->isRequired()) { -// $word = $this->captchaStringResolver->resolve( -// $this->getRequest(), -// $captchaTargetFormName -// ); -// -// $isCorrectCaptcha = $captchaModel->isCorrect($word); -// -// if (!$isCorrectCaptcha) { -// $this->logCaptchaAttempt($captchaModel); -// throw new LocalizedException(__('Incorrect CAPTCHA')); -// } -// } -// -// $this->logCaptchaAttempt($captchaModel); -// } -// -// /** -// * Log captcha attempts -// * -// * @param DefaultModel $captchaModel -// */ -// private function logCaptchaAttempt(DefaultModel $captchaModel) : void -// { -// $email = ''; -// -// if ($this->currentUser->getUserType() == UserContextInterface::USER_TYPE_CUSTOMER) { -// $email = $this->customerRepository->getById($this->currentUser->getUserId())->getEmail(); -// } -// -// $captchaModel->logAttempt($email); -// } } From 8cebb29dd0c26eb0bd3067fe081ceae04d3ac9b2 Mon Sep 17 00:00:00 2001 From: Max Lesechko Date: Thu, 8 Nov 2018 14:40:39 -0600 Subject: [PATCH 056/275] MC-5421: Create test to check dependencies between modules in Declarative Schema --- .../Integrity/DeclarativeDependencyTest.php | 784 ++++++++++++++++++ 1 file changed, 784 insertions(+) create mode 100644 dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php new file mode 100644 index 0000000000000..d1fe1bd57a834 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php @@ -0,0 +1,784 @@ +readJsonFile($root . '/composer.json', true); + if (preg_match('/magento\/project-*/', $rootJson['name']) == 1) { + // The Dependency test is skipped for vendor/magento build + self::markTestSkipped( + 'MAGETWO-43654: The build is running from vendor/magento. DependencyTest is skipped.' + ); + } + } + + /** + * Initialise map of dependencies. + * + * @throws \Exception + */ + private function initDeclaredDependencies() + { + $jsonFiles = Files::init()->getComposerFiles(ComponentRegistrar::MODULE, false); + foreach ($jsonFiles as $file) { + $json = new \Magento\Framework\Config\Composer\Package($this->readJsonFile($file)); + $moduleName = $this->convertModuleName($json->get('name')); + $require = array_keys((array)$json->get('require')); + $this->presetDependencies($moduleName, $require, self::TYPE_HARD); + } + } + + /** + * Read data from json file. + * + * @param string $file + * @return mixed + * @throws \Exception + */ + private function readJsonFile(string $file, bool $asArray = false) + { + $decodedJson = json_decode(file_get_contents($file), $asArray); + if (null == $decodedJson) { + throw new \Exception("Invalid Json: $file"); + } + + return $decodedJson; + } + + /** + * @throws \Exception + */ + public function testUndeclaredDependencies() + { + $this->initDeclaredDependencies(); + $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this); + $invoker( + /** + * Check undeclared modules dependencies for specified file + * + * @param string $fileType + * @param string $file + */ + function ($file) { + $componentRegistrar = new ComponentRegistrar(); + $foundModuleName = ''; + foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) { + if (strpos($file, $moduleDir . '/') !== false) { + $foundModuleName = str_replace('_', '\\', $moduleName); + break; + } + } + if (empty($foundModuleName)) { + return; + } + + $dependencies = $this->getDependenciesFromFiles($file); + $dependencies = $this->filterSelfDependency($foundModuleName, $dependencies); + $undeclaredDependency = $this->collectDependencies($foundModuleName, $dependencies); + + $result = []; + foreach ($undeclaredDependency as $name => $modules) { + $modules = array_unique($modules); + $result[] = $this->getErrorMessage($name) . "\n" . implode("\t\n", $modules); + } + if (count($result)) { + $this->fail( + 'Module ' . $moduleName . ' has undeclared dependencies: ' . "\n" . implode("\t\n", $result) + ); + } + }, + $this->prepareFiles(Files::init()->getDbSchemaFiles()) + ); + } + + /** + * Remove self dependencies. + * + * @param string $moduleName + * @param array $dependencies + * @return array + */ + private function filterSelfDependency(string $moduleName, array $dependencies):array + { + foreach ($dependencies as $id => $modules) { + $decodedId = $this->decodeDependencyId($id); + $entityType = $decodedId['entityType']; + if ($entityType === self::SCHEMA_ENTITY_TABLE || $entityType === "column") { + if (array_search($moduleName, $modules) !== false) { + unset($dependencies[$id]); + } + } else { + $dependencies[$id] = $this->filterComplexDependency($moduleName, $modules); + } + } + + return array_filter($dependencies); + } + + /** + * Remove already declared dependencies. + * + * @param string $moduleName + * @param array $modules + * @return array + */ + private function filterComplexDependency(string $moduleName, array $modules): array + { + $resultDependencies = []; + if (!is_array(reset($modules))) { + if (array_search($moduleName, $modules) === false) { + $resultDependencies = $modules; + } + } else { + foreach ($modules as $dependencySet) { + if (array_search($moduleName, $dependencySet) === false) { + $resultDependencies = array_merge( + $resultDependencies, + $dependencySet + ); + } + } + } + + return array_values(array_unique($resultDependencies)); + } + + /** + * Retrieve declarative schema declaration. + * + * @return array + * @throws \Exception + */ + private function getDeclarativeSchema(): array + { + if ($this->dbSchemaDeclaration) { + return $this->dbSchemaDeclaration; + } + + $entityTypes = [self::SCHEMA_ENTITY_COLUMN, self::SCHEMA_ENTITY_CONSTRAINT, self::SCHEMA_ENTITY_INDEX]; + $declaration = []; + foreach (Files::init()->getDbSchemaFiles() as $filePath) { + $filePath = reset($filePath); + preg_match('#app/code/(\w+/\w+)#', $filePath, $result); + $moduleName = str_replace('/', '\\', $result[1]); + $moduleDeclaration = $this->getDbSchemaDeclaration($filePath); + + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (!isset($tableDeclaration['modules'])) { + $tableDeclaration['modules'] = []; + } + array_push($tableDeclaration['modules'], $moduleName); + $moduleDeclaration = array_replace_recursive( + $moduleDeclaration, + [self::SCHEMA_ENTITY_TABLE => + [ + $tableName => $tableDeclaration, + ] + ] + ); + foreach ($entityTypes as $entityType) { + if (!isset($tableDeclaration[$entityType])) { + continue; + } + $moduleDeclaration = array_replace_recursive( + $moduleDeclaration, + [self::SCHEMA_ENTITY_TABLE => + [ + $tableName => + $this->addModuleAssigment($tableDeclaration, $entityType, $moduleName) + ] + ] + ); + } + } + $declaration = array_merge_recursive($declaration, $moduleDeclaration); + } + $this->dbSchemaDeclaration = $declaration; + + return $this->dbSchemaDeclaration; + } + + /** + * Get declared dependencies. + * + * @param string $tableName + * @param string $entityType + * @param null|string $entityName + * @return array + * @throws \Exception + */ + private function resolveEntityDependencies(string $tableName, string $entityType, ?string $entityName = null): array + { + switch ($entityType) { + case self::SCHEMA_ENTITY_COLUMN: + case self::SCHEMA_ENTITY_CONSTRAINT: + case self::SCHEMA_ENTITY_INDEX: + return $this->getDeclarativeSchema() + [self::SCHEMA_ENTITY_TABLE][$tableName][$entityType][$entityName]['modules']; + break; + case self::SCHEMA_ENTITY_TABLE: + return $this->getDeclarativeSchema()[self::SCHEMA_ENTITY_TABLE][$tableName]['modules']; + break; + default: + return []; + } + } + + /** + * @param string $filePath + * @return array + */ + private function getDbSchemaDeclaration(string $filePath): array + { + $dom = new \DOMDocument(); + $dom->loadXML(file_get_contents($filePath)); + return (new Converter())->convert($dom); + } + + /** + * Add dependency on the current module. + * + * @param array $tableDeclaration + * @param string $entityType + * @param string $moduleName + * @return array + */ + private function addModuleAssigment( + array $tableDeclaration, + string $entityType, + string $moduleName + ): array { + $declarationWithAssigment = []; + foreach ($tableDeclaration[$entityType] as $entityName => $entityDeclaration) { + if (!isset($entityDeclaration['modules'])) { + $entityDeclaration['modules'] = []; + } + if (!$this->isEntityDisabled($entityDeclaration)) { + array_push($entityDeclaration['modules'], $moduleName); + } + + $declarationWithAssigment[$entityType][$entityName] = $entityDeclaration; + } + + return $declarationWithAssigment; + } + + /** + * Retrieve dependencies from files. + * + * @param string $file + * @return string[] + * @throws \Exception + */ + private function getDependenciesFromFiles($file) + { + $moduleDbSchema = $this->getDbSchemaDeclaration($file); + $dependencies = array_merge_recursive( + $this->getDisabledDependencies($moduleDbSchema), + $this->getConstraintDependencies($moduleDbSchema), + $this->getIndexDependencies($moduleDbSchema) + ); + return $dependencies; + } + + /** + * Retrieve dependencies for disabled entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getDisabledDependencies(array $moduleDeclaration): array + { + $disabledDependencies = []; + $entityTypes = [self::SCHEMA_ENTITY_COLUMN, self::SCHEMA_ENTITY_CONSTRAINT, self::SCHEMA_ENTITY_INDEX]; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + foreach ($entityTypes as $entityType) { + if (!isset($tableDeclaration[$entityType])) { + continue; + } + foreach ($tableDeclaration[$entityType] as $entityName => $entityDeclaration) { + if ($this->isEntityDisabled($entityDeclaration)) { + $dependencyIdentifier = $this->getDependencyId($tableName, $entityType, $entityName); + $disabledDependencies[$dependencyIdentifier] = + $this->resolveEntityDependencies($tableName, $entityType, $entityName); + } + } + } + if ($this->isEntityDisabled($tableDeclaration)) { + $disabledDependencies[$this->getDependencyId($tableName)] = + $this->resolveEntityDependencies($tableName, self::SCHEMA_ENTITY_TABLE); + } + } + + return $disabledDependencies; + } + + /** + * Retrieve dependencies for foreign entities. + * + * @param array $constraintDeclaration + * @return array + * @throws \Exception + */ + private function getFKDependencies(array $constraintDeclaration): array + { + $referenceDependencyIdentifier = + $this->getDependencyId( + $constraintDeclaration['referenceTable'], + self::SCHEMA_ENTITY_CONSTRAINT, + $constraintDeclaration['referenceId'] + ); + $dependencyIdentifier = + $this->getDependencyId( + $constraintDeclaration[self::SCHEMA_ENTITY_TABLE], + self::SCHEMA_ENTITY_CONSTRAINT, + $constraintDeclaration['referenceId'] + ); + + $constraintDependencies = []; + $constraintDependencies[$referenceDependencyIdentifier] = + $this->resolveEntityDependencies( + $constraintDeclaration['referenceTable'], + self::SCHEMA_ENTITY_COLUMN, + $constraintDeclaration['referenceColumn'] + ); + $constraintDependencies[$dependencyIdentifier] = + $this->resolveEntityDependencies( + $constraintDeclaration[self::SCHEMA_ENTITY_TABLE], + self::SCHEMA_ENTITY_COLUMN, + $constraintDeclaration[self::SCHEMA_ENTITY_COLUMN] + ); + + return $constraintDependencies; + } + + /** + * Retrieve dependencies for constraint entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getConstraintDependencies(array $moduleDeclaration): array + { + $constraintDependencies = []; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (empty($tableDeclaration[self::SCHEMA_ENTITY_CONSTRAINT])) { + continue; + } + foreach ($tableDeclaration[self::SCHEMA_ENTITY_CONSTRAINT] as $constraintName => $constraintDeclaration) { + if ($this->isEntityDisabled($constraintDeclaration)) { + continue; + } + $dependencyIdentifier = + $this->getDependencyId($tableName, self::SCHEMA_ENTITY_CONSTRAINT, $constraintName); + switch ($constraintDeclaration['type']) { + case 'foreign': + $constraintDependencies = array_merge( + $constraintDependencies, + $this->getFKDependencies($constraintDeclaration) + ); + break; + case 'primary': + case 'unique': + $constraintDependencies[$dependencyIdentifier] = $this->getComplexDependency( + $tableName, + $constraintDeclaration + ); + } + } + } + return $constraintDependencies; + } + + /** + * Calculate complex dependency. + * + * @param string $tableName + * @param array $entityDeclaration + * @return array + * @throws \Exception + */ + private function getComplexDependency(string $tableName, array $entityDeclaration): array + { + $complexDependency = []; + if (empty($entityDeclaration[self::SCHEMA_ENTITY_COLUMN])) { + return $complexDependency; + } + + if (!is_array($entityDeclaration[self::SCHEMA_ENTITY_COLUMN])) { + $entityDeclaration[self::SCHEMA_ENTITY_COLUMN] = [$entityDeclaration[self::SCHEMA_ENTITY_COLUMN]]; + } + + foreach (array_keys($entityDeclaration[self::SCHEMA_ENTITY_COLUMN]) as $columnName) { + $complexDependency[] = + $this->resolveEntityDependencies($tableName, self::SCHEMA_ENTITY_COLUMN, $columnName); + } + + return array_values($complexDependency); + } + + /** + * Retrieve dependencies for index entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getIndexDependencies(array $moduleDeclaration): array + { + $indexDependencies = []; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (empty($tableDeclaration[self::SCHEMA_ENTITY_INDEX])) { + continue; + } + foreach ($tableDeclaration[self::SCHEMA_ENTITY_INDEX] as $indexName => $indexDeclaration) { + if ($this->isEntityDisabled($indexDeclaration)) { + continue; + } + $dependencyIdentifier = + $this->getDependencyId($tableName, self::SCHEMA_ENTITY_INDEX, $indexName); + $indexDependencies[$dependencyIdentifier] = + $this->getComplexDependency($tableName, $indexDeclaration); + } + } + + return $indexDependencies; + } + + /** + * Check status of the entity declaration. + * + * @param array $entityDeclaration + * @return bool + */ + private function isEntityDisabled(array $entityDeclaration): bool + { + return isset($entityDeclaration['disabled']) && $entityDeclaration['disabled'] == true; + } + + /** + * Retrive dependency id. + * + * @param string $tableName + * @param string $entityType + * @param null|string $entityName + * @return string + */ + private function getDependencyId( + string $tableName, + string $entityType = self::SCHEMA_ENTITY_TABLE, + ?string $entityName = null + ) { + return implode('___', [$tableName, $entityType, $entityName ?: $tableName]); + } + + /** + * Retrieve dependency parameters from dependency id. + * + * @param string $id + * @return array + */ + private function decodeDependencyId(string $id): array + { + $decodedValues = explode('___', $id); + $result = [ + 'tableName' => $decodedValues[0], + 'entityType' => $decodedValues[1], + 'entityName' => $decodedValues[2], + ]; + return $result; + } + + /** + * Retrieve error message for dependency. + * + * @param string $id + * @return string + */ + private function getErrorMessage(string $id): string + { + $decodedId = $this->decodeDependencyId($id); + $entityType = $decodedId['entityType']; + if ($entityType === self::SCHEMA_ENTITY_TABLE) { + $message = sprintf( + 'Table %s has undeclared dependency on one of the next modules.', + $decodedId['tableName'] + ); + } else { + $message = sprintf( + '%s %s from %s table has undeclared dependency on one of the next modules.', + ucfirst($entityType), + $decodedId['entityName'], + $decodedId['tableName'] + ); + } + + return $message; + } + + /** + * Collect module dependencies. + * + * @param string $currentModuleName + * @param array $dependencies + * @return array + */ + private function collectDependencies($currentModuleName, $dependencies = []) + { + if (!count($dependencies)) { + return []; + } + foreach ($dependencies as $dependencyName => $dependency) { + $this->collectDependency($dependencyName, $dependency, $currentModuleName); + } + + return $this->getDeclaredDependencies($currentModuleName, self::TYPE_HARD, self::MAP_TYPE_FOUND); + } + + /** + * Collect a module dependency. + * + * @param string $dependencyName + * @param array $dependency + * @param string $currentModule + */ + private function collectDependency( + string $dependencyName, + array $dependency, + string $currentModule + ) { + $declared = $this->getDeclaredDependencies($currentModule, self::TYPE_HARD, self::MAP_TYPE_DECLARED); + $checkResult = array_intersect($declared, $dependency); + + if (empty($checkResult)) { + $this->addDependencies( + $currentModule, + self::TYPE_HARD, + self::MAP_TYPE_FOUND, + [ + $dependencyName => $dependency, + ] + ); + } + } + + /** + * Convert file list to data provider structure. + * + * @param string[] $files + * @return array + */ + private function prepareFiles(array $files): array + { + $result = []; + foreach ($files as $relativePath => $file) { + $absolutePath = reset($file); + $result[$relativePath] = [$absolutePath]; + } + return $result; + } + + /** + * Add dependencies to dependency list. + * + * @param string $moduleName + * @param array $packageNames + * @param string $type + * + * @return void + * @throws \Exception + */ + private function presetDependencies( + string $moduleName, + array $packageNames, + string $type + ): void { + $packageNames = array_filter($packageNames, function ($packageName) { + return $this->getModuleName($packageName) || + 0 === strpos($packageName, 'magento/') && 'magento/magento-composer-installer' != $packageName; + }); + + foreach ($packageNames as $packageName) { + $this->addDependencies( + $moduleName, + $type, + self::MAP_TYPE_DECLARED, + [$this->convertModuleName($packageName)] + ); + } + } + + /** + * Converts a composer json component name into the Magento Module form. + * + * @param string $jsonName The name of a composer json component or dependency e.g. 'magento/module-theme' + * @return string The corresponding Magento Module e.g. 'Magento\Theme' + * @throws \Exception + */ + private function convertModuleName(string $jsonName): string + { + $moduleName = $this->getModuleName($jsonName); + if ($moduleName) { + return $moduleName; + } + + if ( + strpos($jsonName, 'magento/magento') !== false + || strpos($jsonName, 'magento/framework') !== false + ) { + $moduleName = str_replace('/', "\t", $jsonName); + $moduleName = str_replace('framework-', "Framework\t", $moduleName); + $moduleName = str_replace('-', ' ', $moduleName); + $moduleName = ucwords($moduleName); + $moduleName = str_replace("\t", '\\', $moduleName); + $moduleName = str_replace(' ', '', $moduleName); + } else { + $moduleName = $jsonName; + } + + return $moduleName; + } + + /** + * Returns package name on module name mapping. + * + * @return array + * @throws \Exception + */ + private function getPackageModuleMapping(): array + { + if (!$this->packageModuleMapping) { + $jsonFiles = Files::init()->getComposerFiles(ComponentRegistrar::MODULE, false); + + $packageModuleMapping = []; + foreach ($jsonFiles as $file) { + $moduleXml = simplexml_load_file(dirname($file) . '/etc/module.xml'); + $moduleName = str_replace('_', '\\', (string)$moduleXml->module->attributes()->name); + $composerJson = $this->readJsonFile($file); + $packageName = $composerJson->name; + $packageModuleMapping[$packageName] = $moduleName; + } + + $this->packageModuleMapping = $packageModuleMapping; + } + + return $this->packageModuleMapping; + } + + /** + * Retrive Magento style module name. + * + * @param string $packageName + * @return null|string + * @throws \Exception + */ + private function getModuleName(string $packageName): ?string + { + return $this->getPackageModuleMapping()[$packageName] ?? null; + } + + /** + * Retrieve array of dependency items. + * + * @param $module + * @param $type + * @param $mapType + * @return array + */ + private function getDeclaredDependencies(string $module, string $type, string $mapType) + { + return $this->mapDependencies[$module][$type][$mapType] ?? []; + } + + /** + * Add dependency map items. + * + * @param $module + * @param $type + * @param $mapType + * @param $dependencies + */ + protected function addDependencies(string $module, string $type, string $mapType, array $dependencies) + { + $this->mapDependencies[$module][$type][$mapType] = array_merge_recursive( + $this->getDeclaredDependencies($module, $type, $mapType), + $dependencies + ); + } +} \ No newline at end of file From 58b0c691f54d8b3c52090488809cf9b62efc36a1 Mon Sep 17 00:00:00 2001 From: Max Lesechko Date: Fri, 9 Nov 2018 16:32:21 -0600 Subject: [PATCH 057/275] MC-5421: Create test to check dependencies between modules in Declarative Schema --- .../Dependency/DeclarativeSchemaRule.php | 101 --- .../Integrity/DeclarativeDependencyTest.php | 685 +--------------- .../DeclarativeSchemaDependencyProvider.php | 759 ++++++++++++++++++ .../Magento/Test/Integrity/DependencyTest.php | 59 +- 4 files changed, 803 insertions(+), 801 deletions(-) delete mode 100644 dev/tests/static/framework/Magento/TestFramework/Dependency/DeclarativeSchemaRule.php create mode 100644 dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/DeclarativeSchemaRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/DeclarativeSchemaRule.php deleted file mode 100644 index 72e4fff7e5162..0000000000000 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/DeclarativeSchemaRule.php +++ /dev/null @@ -1,101 +0,0 @@ -_moduleTableMap = $tables; - } - - /** - * Gets external dependencies information for current module by analyzing db_schema.xml files contents. - * - * @param string $currentModule - * @param string $fileType - * @param string $file - * @param string $contents - * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public function getDependencyInfo($currentModule, $fileType, $file, &$contents) - { - if ('db_schema' != $fileType || !preg_match('#.*/db_schema\.xml$#', $file)) { - return []; - } - - $dependenciesInfo = []; - $unKnowTables = []; - - $dom = new \DOMDocument(); - $dom->loadXML($contents); - $tables = $dom->getElementsByTagName('table'); - $constraints = $dom->getElementsByTagName('constraint'); - - $tableNames = []; - $foreignKeyTables = []; - $foreignKeyReferenceTables = []; - - /** @var \DOMElement $table */ - foreach ($tables as $table) { - $tableNames[] = $table->getAttribute('name'); - } - - /** @var \DOMElement $constraint */ - foreach ($constraints as $constraint) { - $xsiType = $constraint->getAttribute('xsi:type'); - if (strtolower($xsiType) == 'foreign' && $constraint->getAttribute('disabled') !== '1') { - $foreignKeyTables[] = $constraint->getAttribute('table'); - $foreignKeyReferenceTables[] = $constraint->getAttribute('referenceTable'); - } - } - - $tableNames = array_unique(array_merge($tableNames, $foreignKeyReferenceTables, $foreignKeyTables)); - - /** @var string $table */ - foreach ($tableNames as $table) { - if (!isset($this->_moduleTableMap[$table])) { - $unKnowTables[$file][$table] = $table; - continue; - } - if (strtolower($currentModule) !== strtolower($this->_moduleTableMap[$table])) { - $dependenciesInfo[] = [ - 'module' => $this->_moduleTableMap[$table], - 'type' => RuleInterface::TYPE_HARD, - 'source' => $table, - ]; - } - } - - foreach ($unKnowTables as $tables) { - foreach ($tables as $table) { - $dependenciesInfo[] = ['module' => 'Unknown', 'source' => $table]; - } - } - return $dependenciesInfo; - } -} diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php index d1fe1bd57a834..257da9669d9b5 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DeclarativeDependencyTest.php @@ -8,66 +8,19 @@ namespace Magento\Test\Integrity; +use Magento\Test\Integrity\Dependency\DeclarativeSchemaDependencyProvider; use Magento\Framework\App\Utility\Files; use Magento\Framework\Component\ComponentRegistrar; -use Magento\Framework\Setup\Declaration\Schema\Config\Converter; /** * Class DeclarativeDependencyTest - * - * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) */ class DeclarativeDependencyTest extends \PHPUnit\Framework\TestCase { /** - * Types of dependency between modules. + * @var DeclarativeSchemaDependencyProvider */ - const TYPE_HARD = 'hard'; - - /** - * The identifier of dependency for mapping. - */ - const MAP_TYPE_DECLARED = 'declared'; - - /** - * The identifier of dependency for mapping. - */ - const MAP_TYPE_FOUND = 'found'; - - /** - * Declarative name for table entity of the declarative schema. - */ - const SCHEMA_ENTITY_TABLE = 'table'; - - /** - * Declarative name for column entity of the declarative schema. - */ - const SCHEMA_ENTITY_COLUMN = 'column'; - - /** - * Declarative name for constraint entity of the declarative schema. - */ - const SCHEMA_ENTITY_CONSTRAINT = 'constraint'; - - /** - * Declarative name for index entity of the declarative schema. - */ - const SCHEMA_ENTITY_INDEX = 'index'; - - /** - * @var array - */ - private $mapDependencies = []; - - /** - * @var array - */ - private $dbSchemaDeclaration = []; - - /** - * @var array - */ - private $packageModuleMapping = []; + private $dependencyProvider; /** * Sets up data @@ -84,39 +37,7 @@ protected function setUp() 'MAGETWO-43654: The build is running from vendor/magento. DependencyTest is skipped.' ); } - } - - /** - * Initialise map of dependencies. - * - * @throws \Exception - */ - private function initDeclaredDependencies() - { - $jsonFiles = Files::init()->getComposerFiles(ComponentRegistrar::MODULE, false); - foreach ($jsonFiles as $file) { - $json = new \Magento\Framework\Config\Composer\Package($this->readJsonFile($file)); - $moduleName = $this->convertModuleName($json->get('name')); - $require = array_keys((array)$json->get('require')); - $this->presetDependencies($moduleName, $require, self::TYPE_HARD); - } - } - - /** - * Read data from json file. - * - * @param string $file - * @return mixed - * @throws \Exception - */ - private function readJsonFile(string $file, bool $asArray = false) - { - $decodedJson = json_decode(file_get_contents($file), $asArray); - if (null == $decodedJson) { - throw new \Exception("Invalid Json: $file"); - } - - return $decodedJson; + $this->dependencyProvider = new DeclarativeSchemaDependencyProvider(); } /** @@ -124,7 +45,6 @@ private function readJsonFile(string $file, bool $asArray = false) */ public function testUndeclaredDependencies() { - $this->initDeclaredDependencies(); $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this); $invoker( /** @@ -146,9 +66,7 @@ function ($file) { return; } - $dependencies = $this->getDependenciesFromFiles($file); - $dependencies = $this->filterSelfDependency($foundModuleName, $dependencies); - $undeclaredDependency = $this->collectDependencies($foundModuleName, $dependencies); + $undeclaredDependency = $this->dependencyProvider->getUndeclaredModuleDependencies($foundModuleName); $result = []; foreach ($undeclaredDependency as $name => $modules) { @@ -166,402 +84,18 @@ function ($file) { } /** - * Remove self dependencies. - * - * @param string $moduleName - * @param array $dependencies - * @return array - */ - private function filterSelfDependency(string $moduleName, array $dependencies):array - { - foreach ($dependencies as $id => $modules) { - $decodedId = $this->decodeDependencyId($id); - $entityType = $decodedId['entityType']; - if ($entityType === self::SCHEMA_ENTITY_TABLE || $entityType === "column") { - if (array_search($moduleName, $modules) !== false) { - unset($dependencies[$id]); - } - } else { - $dependencies[$id] = $this->filterComplexDependency($moduleName, $modules); - } - } - - return array_filter($dependencies); - } - - /** - * Remove already declared dependencies. - * - * @param string $moduleName - * @param array $modules - * @return array - */ - private function filterComplexDependency(string $moduleName, array $modules): array - { - $resultDependencies = []; - if (!is_array(reset($modules))) { - if (array_search($moduleName, $modules) === false) { - $resultDependencies = $modules; - } - } else { - foreach ($modules as $dependencySet) { - if (array_search($moduleName, $dependencySet) === false) { - $resultDependencies = array_merge( - $resultDependencies, - $dependencySet - ); - } - } - } - - return array_values(array_unique($resultDependencies)); - } - - /** - * Retrieve declarative schema declaration. - * - * @return array - * @throws \Exception - */ - private function getDeclarativeSchema(): array - { - if ($this->dbSchemaDeclaration) { - return $this->dbSchemaDeclaration; - } - - $entityTypes = [self::SCHEMA_ENTITY_COLUMN, self::SCHEMA_ENTITY_CONSTRAINT, self::SCHEMA_ENTITY_INDEX]; - $declaration = []; - foreach (Files::init()->getDbSchemaFiles() as $filePath) { - $filePath = reset($filePath); - preg_match('#app/code/(\w+/\w+)#', $filePath, $result); - $moduleName = str_replace('/', '\\', $result[1]); - $moduleDeclaration = $this->getDbSchemaDeclaration($filePath); - - foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { - if (!isset($tableDeclaration['modules'])) { - $tableDeclaration['modules'] = []; - } - array_push($tableDeclaration['modules'], $moduleName); - $moduleDeclaration = array_replace_recursive( - $moduleDeclaration, - [self::SCHEMA_ENTITY_TABLE => - [ - $tableName => $tableDeclaration, - ] - ] - ); - foreach ($entityTypes as $entityType) { - if (!isset($tableDeclaration[$entityType])) { - continue; - } - $moduleDeclaration = array_replace_recursive( - $moduleDeclaration, - [self::SCHEMA_ENTITY_TABLE => - [ - $tableName => - $this->addModuleAssigment($tableDeclaration, $entityType, $moduleName) - ] - ] - ); - } - } - $declaration = array_merge_recursive($declaration, $moduleDeclaration); - } - $this->dbSchemaDeclaration = $declaration; - - return $this->dbSchemaDeclaration; - } - - /** - * Get declared dependencies. - * - * @param string $tableName - * @param string $entityType - * @param null|string $entityName - * @return array - * @throws \Exception - */ - private function resolveEntityDependencies(string $tableName, string $entityType, ?string $entityName = null): array - { - switch ($entityType) { - case self::SCHEMA_ENTITY_COLUMN: - case self::SCHEMA_ENTITY_CONSTRAINT: - case self::SCHEMA_ENTITY_INDEX: - return $this->getDeclarativeSchema() - [self::SCHEMA_ENTITY_TABLE][$tableName][$entityType][$entityName]['modules']; - break; - case self::SCHEMA_ENTITY_TABLE: - return $this->getDeclarativeSchema()[self::SCHEMA_ENTITY_TABLE][$tableName]['modules']; - break; - default: - return []; - } - } - - /** - * @param string $filePath - * @return array - */ - private function getDbSchemaDeclaration(string $filePath): array - { - $dom = new \DOMDocument(); - $dom->loadXML(file_get_contents($filePath)); - return (new Converter())->convert($dom); - } - - /** - * Add dependency on the current module. - * - * @param array $tableDeclaration - * @param string $entityType - * @param string $moduleName - * @return array - */ - private function addModuleAssigment( - array $tableDeclaration, - string $entityType, - string $moduleName - ): array { - $declarationWithAssigment = []; - foreach ($tableDeclaration[$entityType] as $entityName => $entityDeclaration) { - if (!isset($entityDeclaration['modules'])) { - $entityDeclaration['modules'] = []; - } - if (!$this->isEntityDisabled($entityDeclaration)) { - array_push($entityDeclaration['modules'], $moduleName); - } - - $declarationWithAssigment[$entityType][$entityName] = $entityDeclaration; - } - - return $declarationWithAssigment; - } - - /** - * Retrieve dependencies from files. - * - * @param string $file - * @return string[] - * @throws \Exception - */ - private function getDependenciesFromFiles($file) - { - $moduleDbSchema = $this->getDbSchemaDeclaration($file); - $dependencies = array_merge_recursive( - $this->getDisabledDependencies($moduleDbSchema), - $this->getConstraintDependencies($moduleDbSchema), - $this->getIndexDependencies($moduleDbSchema) - ); - return $dependencies; - } - - /** - * Retrieve dependencies for disabled entities. - * - * @param array $moduleDeclaration - * @return array - * @throws \Exception - */ - private function getDisabledDependencies(array $moduleDeclaration): array - { - $disabledDependencies = []; - $entityTypes = [self::SCHEMA_ENTITY_COLUMN, self::SCHEMA_ENTITY_CONSTRAINT, self::SCHEMA_ENTITY_INDEX]; - foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { - foreach ($entityTypes as $entityType) { - if (!isset($tableDeclaration[$entityType])) { - continue; - } - foreach ($tableDeclaration[$entityType] as $entityName => $entityDeclaration) { - if ($this->isEntityDisabled($entityDeclaration)) { - $dependencyIdentifier = $this->getDependencyId($tableName, $entityType, $entityName); - $disabledDependencies[$dependencyIdentifier] = - $this->resolveEntityDependencies($tableName, $entityType, $entityName); - } - } - } - if ($this->isEntityDisabled($tableDeclaration)) { - $disabledDependencies[$this->getDependencyId($tableName)] = - $this->resolveEntityDependencies($tableName, self::SCHEMA_ENTITY_TABLE); - } - } - - return $disabledDependencies; - } - - /** - * Retrieve dependencies for foreign entities. - * - * @param array $constraintDeclaration - * @return array - * @throws \Exception - */ - private function getFKDependencies(array $constraintDeclaration): array - { - $referenceDependencyIdentifier = - $this->getDependencyId( - $constraintDeclaration['referenceTable'], - self::SCHEMA_ENTITY_CONSTRAINT, - $constraintDeclaration['referenceId'] - ); - $dependencyIdentifier = - $this->getDependencyId( - $constraintDeclaration[self::SCHEMA_ENTITY_TABLE], - self::SCHEMA_ENTITY_CONSTRAINT, - $constraintDeclaration['referenceId'] - ); - - $constraintDependencies = []; - $constraintDependencies[$referenceDependencyIdentifier] = - $this->resolveEntityDependencies( - $constraintDeclaration['referenceTable'], - self::SCHEMA_ENTITY_COLUMN, - $constraintDeclaration['referenceColumn'] - ); - $constraintDependencies[$dependencyIdentifier] = - $this->resolveEntityDependencies( - $constraintDeclaration[self::SCHEMA_ENTITY_TABLE], - self::SCHEMA_ENTITY_COLUMN, - $constraintDeclaration[self::SCHEMA_ENTITY_COLUMN] - ); - - return $constraintDependencies; - } - - /** - * Retrieve dependencies for constraint entities. - * - * @param array $moduleDeclaration - * @return array - * @throws \Exception - */ - private function getConstraintDependencies(array $moduleDeclaration): array - { - $constraintDependencies = []; - foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { - if (empty($tableDeclaration[self::SCHEMA_ENTITY_CONSTRAINT])) { - continue; - } - foreach ($tableDeclaration[self::SCHEMA_ENTITY_CONSTRAINT] as $constraintName => $constraintDeclaration) { - if ($this->isEntityDisabled($constraintDeclaration)) { - continue; - } - $dependencyIdentifier = - $this->getDependencyId($tableName, self::SCHEMA_ENTITY_CONSTRAINT, $constraintName); - switch ($constraintDeclaration['type']) { - case 'foreign': - $constraintDependencies = array_merge( - $constraintDependencies, - $this->getFKDependencies($constraintDeclaration) - ); - break; - case 'primary': - case 'unique': - $constraintDependencies[$dependencyIdentifier] = $this->getComplexDependency( - $tableName, - $constraintDeclaration - ); - } - } - } - return $constraintDependencies; - } - - /** - * Calculate complex dependency. - * - * @param string $tableName - * @param array $entityDeclaration - * @return array - * @throws \Exception - */ - private function getComplexDependency(string $tableName, array $entityDeclaration): array - { - $complexDependency = []; - if (empty($entityDeclaration[self::SCHEMA_ENTITY_COLUMN])) { - return $complexDependency; - } - - if (!is_array($entityDeclaration[self::SCHEMA_ENTITY_COLUMN])) { - $entityDeclaration[self::SCHEMA_ENTITY_COLUMN] = [$entityDeclaration[self::SCHEMA_ENTITY_COLUMN]]; - } - - foreach (array_keys($entityDeclaration[self::SCHEMA_ENTITY_COLUMN]) as $columnName) { - $complexDependency[] = - $this->resolveEntityDependencies($tableName, self::SCHEMA_ENTITY_COLUMN, $columnName); - } - - return array_values($complexDependency); - } - - /** - * Retrieve dependencies for index entities. + * Convert file list to data provider structure. * - * @param array $moduleDeclaration + * @param string[] $files * @return array - * @throws \Exception */ - private function getIndexDependencies(array $moduleDeclaration): array + private function prepareFiles(array $files): array { - $indexDependencies = []; - foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { - if (empty($tableDeclaration[self::SCHEMA_ENTITY_INDEX])) { - continue; - } - foreach ($tableDeclaration[self::SCHEMA_ENTITY_INDEX] as $indexName => $indexDeclaration) { - if ($this->isEntityDisabled($indexDeclaration)) { - continue; - } - $dependencyIdentifier = - $this->getDependencyId($tableName, self::SCHEMA_ENTITY_INDEX, $indexName); - $indexDependencies[$dependencyIdentifier] = - $this->getComplexDependency($tableName, $indexDeclaration); - } + $result = []; + foreach ($files as $relativePath => $file) { + $absolutePath = reset($file); + $result[$relativePath] = [$absolutePath]; } - - return $indexDependencies; - } - - /** - * Check status of the entity declaration. - * - * @param array $entityDeclaration - * @return bool - */ - private function isEntityDisabled(array $entityDeclaration): bool - { - return isset($entityDeclaration['disabled']) && $entityDeclaration['disabled'] == true; - } - - /** - * Retrive dependency id. - * - * @param string $tableName - * @param string $entityType - * @param null|string $entityName - * @return string - */ - private function getDependencyId( - string $tableName, - string $entityType = self::SCHEMA_ENTITY_TABLE, - ?string $entityName = null - ) { - return implode('___', [$tableName, $entityType, $entityName ?: $tableName]); - } - - /** - * Retrieve dependency parameters from dependency id. - * - * @param string $id - * @return array - */ - private function decodeDependencyId(string $id): array - { - $decodedValues = explode('___', $id); - $result = [ - 'tableName' => $decodedValues[0], - 'entityType' => $decodedValues[1], - 'entityName' => $decodedValues[2], - ]; return $result; } @@ -573,9 +107,9 @@ private function decodeDependencyId(string $id): array */ private function getErrorMessage(string $id): string { - $decodedId = $this->decodeDependencyId($id); + $decodedId = $this->dependencyProvider->decodeDependencyId($id); $entityType = $decodedId['entityType']; - if ($entityType === self::SCHEMA_ENTITY_TABLE) { + if ($entityType === DeclarativeSchemaDependencyProvider::SCHEMA_ENTITY_TABLE) { $message = sprintf( 'Table %s has undeclared dependency on one of the next modules.', $decodedId['tableName'] @@ -593,192 +127,19 @@ private function getErrorMessage(string $id): string } /** - * Collect module dependencies. - * - * @param string $currentModuleName - * @param array $dependencies - * @return array - */ - private function collectDependencies($currentModuleName, $dependencies = []) - { - if (!count($dependencies)) { - return []; - } - foreach ($dependencies as $dependencyName => $dependency) { - $this->collectDependency($dependencyName, $dependency, $currentModuleName); - } - - return $this->getDeclaredDependencies($currentModuleName, self::TYPE_HARD, self::MAP_TYPE_FOUND); - } - - /** - * Collect a module dependency. - * - * @param string $dependencyName - * @param array $dependency - * @param string $currentModule - */ - private function collectDependency( - string $dependencyName, - array $dependency, - string $currentModule - ) { - $declared = $this->getDeclaredDependencies($currentModule, self::TYPE_HARD, self::MAP_TYPE_DECLARED); - $checkResult = array_intersect($declared, $dependency); - - if (empty($checkResult)) { - $this->addDependencies( - $currentModule, - self::TYPE_HARD, - self::MAP_TYPE_FOUND, - [ - $dependencyName => $dependency, - ] - ); - } - } - - /** - * Convert file list to data provider structure. - * - * @param string[] $files - * @return array - */ - private function prepareFiles(array $files): array - { - $result = []; - foreach ($files as $relativePath => $file) { - $absolutePath = reset($file); - $result[$relativePath] = [$absolutePath]; - } - return $result; - } - - /** - * Add dependencies to dependency list. - * - * @param string $moduleName - * @param array $packageNames - * @param string $type - * - * @return void - * @throws \Exception - */ - private function presetDependencies( - string $moduleName, - array $packageNames, - string $type - ): void { - $packageNames = array_filter($packageNames, function ($packageName) { - return $this->getModuleName($packageName) || - 0 === strpos($packageName, 'magento/') && 'magento/magento-composer-installer' != $packageName; - }); - - foreach ($packageNames as $packageName) { - $this->addDependencies( - $moduleName, - $type, - self::MAP_TYPE_DECLARED, - [$this->convertModuleName($packageName)] - ); - } - } - - /** - * Converts a composer json component name into the Magento Module form. - * - * @param string $jsonName The name of a composer json component or dependency e.g. 'magento/module-theme' - * @return string The corresponding Magento Module e.g. 'Magento\Theme' - * @throws \Exception - */ - private function convertModuleName(string $jsonName): string - { - $moduleName = $this->getModuleName($jsonName); - if ($moduleName) { - return $moduleName; - } - - if ( - strpos($jsonName, 'magento/magento') !== false - || strpos($jsonName, 'magento/framework') !== false - ) { - $moduleName = str_replace('/', "\t", $jsonName); - $moduleName = str_replace('framework-', "Framework\t", $moduleName); - $moduleName = str_replace('-', ' ', $moduleName); - $moduleName = ucwords($moduleName); - $moduleName = str_replace("\t", '\\', $moduleName); - $moduleName = str_replace(' ', '', $moduleName); - } else { - $moduleName = $jsonName; - } - - return $moduleName; - } - - /** - * Returns package name on module name mapping. + * Read data from json file. * - * @return array + * @param string $file + * @return mixed * @throws \Exception */ - private function getPackageModuleMapping(): array + private function readJsonFile(string $file, bool $asArray = false) { - if (!$this->packageModuleMapping) { - $jsonFiles = Files::init()->getComposerFiles(ComponentRegistrar::MODULE, false); - - $packageModuleMapping = []; - foreach ($jsonFiles as $file) { - $moduleXml = simplexml_load_file(dirname($file) . '/etc/module.xml'); - $moduleName = str_replace('_', '\\', (string)$moduleXml->module->attributes()->name); - $composerJson = $this->readJsonFile($file); - $packageName = $composerJson->name; - $packageModuleMapping[$packageName] = $moduleName; - } - - $this->packageModuleMapping = $packageModuleMapping; + $decodedJson = json_decode(file_get_contents($file), $asArray); + if (null == $decodedJson) { + throw new \Exception("Invalid Json: $file"); } - return $this->packageModuleMapping; - } - - /** - * Retrive Magento style module name. - * - * @param string $packageName - * @return null|string - * @throws \Exception - */ - private function getModuleName(string $packageName): ?string - { - return $this->getPackageModuleMapping()[$packageName] ?? null; - } - - /** - * Retrieve array of dependency items. - * - * @param $module - * @param $type - * @param $mapType - * @return array - */ - private function getDeclaredDependencies(string $module, string $type, string $mapType) - { - return $this->mapDependencies[$module][$type][$mapType] ?? []; - } - - /** - * Add dependency map items. - * - * @param $module - * @param $type - * @param $mapType - * @param $dependencies - */ - protected function addDependencies(string $module, string $type, string $mapType, array $dependencies) - { - $this->mapDependencies[$module][$type][$mapType] = array_merge_recursive( - $this->getDeclaredDependencies($module, $type, $mapType), - $dependencies - ); + return $decodedJson; } -} \ No newline at end of file +} diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php new file mode 100644 index 0000000000000..b9050721c6910 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php @@ -0,0 +1,759 @@ +initDeclaredDependencies(); + $dependencies = $this->getDependenciesFromFiles($this->getSchemaFileNameByModuleName($moduleName)); + $dependencies = $this->filterSelfDependency($moduleName, $dependencies); + $declared = $this->getDeclaredDependencies($moduleName, self::TYPE_HARD, self::MAP_TYPE_DECLARED); + + $existingDeclared = []; + foreach ($dependencies as $dependency) { + $checkResult = array_intersect($declared, $dependency); + if ($checkResult) { + $existingDeclared = array_merge(array_values($checkResult)); + } + } + + return array_unique($existingDeclared); + } + + /** + * Provide undeclared dependencies between modules based on the declarative schema configuration. + * + * [ + * $dependencyId => [$module1, $module2, $module3 ...], + * ... + * ] + * + * @param string $moduleName + * @return array + * @throws \Exception + */ + public function getUndeclaredModuleDependencies(string $moduleName): array + { + $this->initDeclaredDependencies(); + $dependencies = $this->getDependenciesFromFiles($this->getSchemaFileNameByModuleName($moduleName)); + $dependencies = $this->filterSelfDependency($moduleName, $dependencies); + return $this->collectDependencies($moduleName, $dependencies); + } + + /** + * Provide schema file name by module name. + * + * @param string $module + * @return string + * @throws \Exception + */ + private function getSchemaFileNameByModuleName(string $module): string + { + if (empty($this->moduleSchemaFileMapping)) { + $componentRegistrar = new ComponentRegistrar(); + foreach (array_values(Files::init()->getDbSchemaFiles()) as $filePath) { + $filePath = reset($filePath); + foreach ($componentRegistrar->getPaths(ComponentRegistrar::MODULE) as $moduleName => $moduleDir) { + if (strpos($filePath, $moduleDir . '/') !== false) { + $foundModuleName = str_replace('_', '\\', $moduleName); + $this->moduleSchemaFileMapping[$foundModuleName] = $filePath; + break; + } + } + } + } + + return $this->moduleSchemaFileMapping[$module] ?? ''; + } + + /** + * Initialise map of dependencies. + * + * @throws \Exception + */ + private function initDeclaredDependencies() + { + if (empty($this->mapDependencies)) { + $jsonFiles = Files::init()->getComposerFiles(ComponentRegistrar::MODULE, false); + foreach ($jsonFiles as $file) { + $json = new \Magento\Framework\Config\Composer\Package($this->readJsonFile($file)); + $moduleName = $this->convertModuleName($json->get('name')); + $require = array_keys((array)$json->get('require')); + $this->presetDependencies($moduleName, $require, self::TYPE_HARD); + } + } + } + + /** + * Read data from json file. + * + * @param string $file + * @return mixed + * @throws \Exception + */ + private function readJsonFile(string $file, bool $asArray = false) + { + $decodedJson = json_decode(file_get_contents($file), $asArray); + if (null == $decodedJson) { + throw new \Exception("Invalid Json: $file"); + } + + return $decodedJson; + } + + /** + * Remove self dependencies. + * + * @param string $moduleName + * @param array $dependencies + * @return array + */ + private function filterSelfDependency(string $moduleName, array $dependencies):array + { + foreach ($dependencies as $id => $modules) { + $decodedId = $this->decodeDependencyId($id); + $entityType = $decodedId['entityType']; + if ($entityType === self::SCHEMA_ENTITY_TABLE || $entityType === "column") { + if (array_search($moduleName, $modules) !== false) { + unset($dependencies[$id]); + } + } else { + $dependencies[$id] = $this->filterComplexDependency($moduleName, $modules); + } + } + + return array_filter($dependencies); + } + + /** + * Remove already declared dependencies. + * + * @param string $moduleName + * @param array $modules + * @return array + */ + private function filterComplexDependency(string $moduleName, array $modules): array + { + $resultDependencies = []; + if (!is_array(reset($modules))) { + if (array_search($moduleName, $modules) === false) { + $resultDependencies = $modules; + } + } else { + foreach ($modules as $dependencySet) { + if (array_search($moduleName, $dependencySet) === false) { + $resultDependencies = array_merge( + $resultDependencies, + $dependencySet + ); + } + } + } + + return array_values(array_unique($resultDependencies)); + } + + /** + * Retrieve declarative schema declaration. + * + * @return array + * @throws \Exception + */ + private function getDeclarativeSchema(): array + { + if ($this->dbSchemaDeclaration) { + return $this->dbSchemaDeclaration; + } + + $entityTypes = [self::SCHEMA_ENTITY_COLUMN, self::SCHEMA_ENTITY_CONSTRAINT, self::SCHEMA_ENTITY_INDEX]; + $declaration = []; + foreach (Files::init()->getDbSchemaFiles() as $filePath) { + $filePath = reset($filePath); + preg_match('#app/code/(\w+/\w+)#', $filePath, $result); + $moduleName = str_replace('/', '\\', $result[1]); + $moduleDeclaration = $this->getDbSchemaDeclaration($filePath); + + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (!isset($tableDeclaration['modules'])) { + $tableDeclaration['modules'] = []; + } + array_push($tableDeclaration['modules'], $moduleName); + $moduleDeclaration = array_replace_recursive( + $moduleDeclaration, + [self::SCHEMA_ENTITY_TABLE => + [ + $tableName => $tableDeclaration, + ] + ] + ); + foreach ($entityTypes as $entityType) { + if (!isset($tableDeclaration[$entityType])) { + continue; + } + $moduleDeclaration = array_replace_recursive( + $moduleDeclaration, + [self::SCHEMA_ENTITY_TABLE => + [ + $tableName => + $this->addModuleAssigment($tableDeclaration, $entityType, $moduleName) + ] + ] + ); + } + } + $declaration = array_merge_recursive($declaration, $moduleDeclaration); + } + $this->dbSchemaDeclaration = $declaration; + + return $this->dbSchemaDeclaration; + } + + /** + * Get declared dependencies. + * + * @param string $tableName + * @param string $entityType + * @param null|string $entityName + * @return array + * @throws \Exception + */ + private function resolveEntityDependencies(string $tableName, string $entityType, ?string $entityName = null): array + { + switch ($entityType) { + case self::SCHEMA_ENTITY_COLUMN: + case self::SCHEMA_ENTITY_CONSTRAINT: + case self::SCHEMA_ENTITY_INDEX: + return $this->getDeclarativeSchema() + [self::SCHEMA_ENTITY_TABLE][$tableName][$entityType][$entityName]['modules']; + break; + case self::SCHEMA_ENTITY_TABLE: + return $this->getDeclarativeSchema()[self::SCHEMA_ENTITY_TABLE][$tableName]['modules']; + break; + default: + return []; + } + } + + /** + * @param string $filePath + * @return array + */ + private function getDbSchemaDeclaration(string $filePath): array + { + $dom = new \DOMDocument(); + $dom->loadXML(file_get_contents($filePath)); + return (new Converter())->convert($dom); + } + + /** + * Add dependency on the current module. + * + * @param array $tableDeclaration + * @param string $entityType + * @param string $moduleName + * @return array + */ + private function addModuleAssigment( + array $tableDeclaration, + string $entityType, + string $moduleName + ): array { + $declarationWithAssigment = []; + foreach ($tableDeclaration[$entityType] as $entityName => $entityDeclaration) { + if (!isset($entityDeclaration['modules'])) { + $entityDeclaration['modules'] = []; + } + if (!$this->isEntityDisabled($entityDeclaration)) { + array_push($entityDeclaration['modules'], $moduleName); + } + + $declarationWithAssigment[$entityType][$entityName] = $entityDeclaration; + } + + return $declarationWithAssigment; + } + + /** + * Retrieve dependencies from files. + * + * @param string $file + * @return string[] + * @throws \Exception + */ + private function getDependenciesFromFiles($file) + { + if (!$file) { + return []; + } + + $moduleDbSchema = $this->getDbSchemaDeclaration($file); + $dependencies = array_merge_recursive( + $this->getDisabledDependencies($moduleDbSchema), + $this->getConstraintDependencies($moduleDbSchema), + $this->getIndexDependencies($moduleDbSchema) + ); + return $dependencies; + } + + /** + * Retrieve dependencies for disabled entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getDisabledDependencies(array $moduleDeclaration): array + { + $disabledDependencies = []; + $entityTypes = [self::SCHEMA_ENTITY_COLUMN, self::SCHEMA_ENTITY_CONSTRAINT, self::SCHEMA_ENTITY_INDEX]; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + foreach ($entityTypes as $entityType) { + if (!isset($tableDeclaration[$entityType])) { + continue; + } + foreach ($tableDeclaration[$entityType] as $entityName => $entityDeclaration) { + if ($this->isEntityDisabled($entityDeclaration)) { + $dependencyIdentifier = $this->getDependencyId($tableName, $entityType, $entityName); + $disabledDependencies[$dependencyIdentifier] = + $this->resolveEntityDependencies($tableName, $entityType, $entityName); + } + } + } + if ($this->isEntityDisabled($tableDeclaration)) { + $disabledDependencies[$this->getDependencyId($tableName)] = + $this->resolveEntityDependencies($tableName, self::SCHEMA_ENTITY_TABLE); + } + } + + return $disabledDependencies; + } + + /** + * Retrieve dependencies for foreign entities. + * + * @param array $constraintDeclaration + * @return array + * @throws \Exception + */ + private function getFKDependencies(array $constraintDeclaration): array + { + $referenceDependencyIdentifier = + $this->getDependencyId( + $constraintDeclaration['referenceTable'], + self::SCHEMA_ENTITY_CONSTRAINT, + $constraintDeclaration['referenceId'] + ); + $dependencyIdentifier = + $this->getDependencyId( + $constraintDeclaration[self::SCHEMA_ENTITY_TABLE], + self::SCHEMA_ENTITY_CONSTRAINT, + $constraintDeclaration['referenceId'] + ); + + $constraintDependencies = []; + $constraintDependencies[$referenceDependencyIdentifier] = + $this->resolveEntityDependencies( + $constraintDeclaration['referenceTable'], + self::SCHEMA_ENTITY_COLUMN, + $constraintDeclaration['referenceColumn'] + ); + $constraintDependencies[$dependencyIdentifier] = + $this->resolveEntityDependencies( + $constraintDeclaration[self::SCHEMA_ENTITY_TABLE], + self::SCHEMA_ENTITY_COLUMN, + $constraintDeclaration[self::SCHEMA_ENTITY_COLUMN] + ); + + return $constraintDependencies; + } + + /** + * Retrieve dependencies for constraint entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getConstraintDependencies(array $moduleDeclaration): array + { + $constraintDependencies = []; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (empty($tableDeclaration[self::SCHEMA_ENTITY_CONSTRAINT])) { + continue; + } + foreach ($tableDeclaration[self::SCHEMA_ENTITY_CONSTRAINT] as $constraintName => $constraintDeclaration) { + if ($this->isEntityDisabled($constraintDeclaration)) { + continue; + } + $dependencyIdentifier = + $this->getDependencyId($tableName, self::SCHEMA_ENTITY_CONSTRAINT, $constraintName); + switch ($constraintDeclaration['type']) { + case 'foreign': + $constraintDependencies = array_merge( + $constraintDependencies, + $this->getFKDependencies($constraintDeclaration) + ); + break; + case 'primary': + case 'unique': + $constraintDependencies[$dependencyIdentifier] = $this->getComplexDependency( + $tableName, + $constraintDeclaration + ); + } + } + } + return $constraintDependencies; + } + + /** + * Calculate complex dependency. + * + * @param string $tableName + * @param array $entityDeclaration + * @return array + * @throws \Exception + */ + private function getComplexDependency(string $tableName, array $entityDeclaration): array + { + $complexDependency = []; + if (empty($entityDeclaration[self::SCHEMA_ENTITY_COLUMN])) { + return $complexDependency; + } + + if (!is_array($entityDeclaration[self::SCHEMA_ENTITY_COLUMN])) { + $entityDeclaration[self::SCHEMA_ENTITY_COLUMN] = [$entityDeclaration[self::SCHEMA_ENTITY_COLUMN]]; + } + + foreach (array_keys($entityDeclaration[self::SCHEMA_ENTITY_COLUMN]) as $columnName) { + $complexDependency[] = + $this->resolveEntityDependencies($tableName, self::SCHEMA_ENTITY_COLUMN, $columnName); + } + + return array_values($complexDependency); + } + + /** + * Retrieve dependencies for index entities. + * + * @param array $moduleDeclaration + * @return array + * @throws \Exception + */ + private function getIndexDependencies(array $moduleDeclaration): array + { + $indexDependencies = []; + foreach ($moduleDeclaration[self::SCHEMA_ENTITY_TABLE] as $tableName => $tableDeclaration) { + if (empty($tableDeclaration[self::SCHEMA_ENTITY_INDEX])) { + continue; + } + foreach ($tableDeclaration[self::SCHEMA_ENTITY_INDEX] as $indexName => $indexDeclaration) { + if ($this->isEntityDisabled($indexDeclaration)) { + continue; + } + $dependencyIdentifier = + $this->getDependencyId($tableName, self::SCHEMA_ENTITY_INDEX, $indexName); + $indexDependencies[$dependencyIdentifier] = + $this->getComplexDependency($tableName, $indexDeclaration); + } + } + + return $indexDependencies; + } + + /** + * Check status of the entity declaration. + * + * @param array $entityDeclaration + * @return bool + */ + private function isEntityDisabled(array $entityDeclaration): bool + { + return isset($entityDeclaration['disabled']) && $entityDeclaration['disabled'] == true; + } + + /** + * Retrieve dependency id. + * + * @param string $tableName + * @param string $entityType + * @param null|string $entityName + * @return string + */ + private function getDependencyId( + string $tableName, + string $entityType = self::SCHEMA_ENTITY_TABLE, + ?string $entityName = null + ) { + return implode('___', [$tableName, $entityType, $entityName ?: $tableName]); + } + + /** + * Retrieve dependency parameters from dependency id. + * + * @param string $id + * @return array + */ + public static function decodeDependencyId(string $id): array + { + $decodedValues = explode('___', $id); + $result = [ + 'tableName' => $decodedValues[0], + 'entityType' => $decodedValues[1], + 'entityName' => $decodedValues[2], + ]; + return $result; + } + + /** + * Collect module dependencies. + * + * @param string $currentModuleName + * @param array $dependencies + * @return array + */ + private function collectDependencies($currentModuleName, $dependencies = []) + { + if (!count($dependencies)) { + return []; + } + foreach ($dependencies as $dependencyName => $dependency) { + $this->collectDependency($dependencyName, $dependency, $currentModuleName); + } + + return $this->getDeclaredDependencies($currentModuleName, self::TYPE_HARD, self::MAP_TYPE_FOUND); + } + + /** + * Collect a module dependency. + * + * @param string $dependencyName + * @param array $dependency + * @param string $currentModule + */ + private function collectDependency( + string $dependencyName, + array $dependency, + string $currentModule + ) { + $declared = $this->getDeclaredDependencies($currentModule, self::TYPE_HARD, self::MAP_TYPE_DECLARED); + $checkResult = array_intersect($declared, $dependency); + + if (empty($checkResult)) { + $this->addDependencies( + $currentModule, + self::TYPE_HARD, + self::MAP_TYPE_FOUND, + [ + $dependencyName => $dependency, + ] + ); + } + } + + /** + * Add dependencies to dependency list. + * + * @param string $moduleName + * @param array $packageNames + * @param string $type + * + * @return void + * @throws \Exception + */ + private function presetDependencies( + string $moduleName, + array $packageNames, + string $type + ): void { + $packageNames = array_filter($packageNames, function ($packageName) { + return $this->getModuleName($packageName) || + 0 === strpos($packageName, 'magento/') && 'magento/magento-composer-installer' != $packageName; + }); + + foreach ($packageNames as $packageName) { + $this->addDependencies( + $moduleName, + $type, + self::MAP_TYPE_DECLARED, + [$this->convertModuleName($packageName)] + ); + } + } + + /** + * Returns package name on module name mapping. + * + * @return array + * @throws \Exception + */ + private function getPackageModuleMapping(): array + { + if (!$this->packageModuleMapping) { + $jsonFiles = Files::init()->getComposerFiles(ComponentRegistrar::MODULE, false); + + $packageModuleMapping = []; + foreach ($jsonFiles as $file) { + $moduleXml = simplexml_load_file(dirname($file) . '/etc/module.xml'); + $moduleName = str_replace('_', '\\', (string)$moduleXml->module->attributes()->name); + $composerJson = $this->readJsonFile($file); + $packageName = $composerJson->name; + $packageModuleMapping[$packageName] = $moduleName; + } + + $this->packageModuleMapping = $packageModuleMapping; + } + + return $this->packageModuleMapping; + } + + /** + * Retrieve Magento style module name. + * + * @param string $packageName + * @return null|string + * @throws \Exception + */ + private function getModuleName(string $packageName): ?string + { + return $this->getPackageModuleMapping()[$packageName] ?? null; + } + + /** + * Retrieve array of dependency items. + * + * @param $module + * @param $type + * @param $mapType + * @return array + */ + private function getDeclaredDependencies(string $module, string $type, string $mapType) + { + return $this->mapDependencies[$module][$type][$mapType] ?? []; + } + + /** + * Add dependency map items. + * + * @param $module + * @param $type + * @param $mapType + * @param $dependencies + */ + protected function addDependencies(string $module, string $type, string $mapType, array $dependencies) + { + $this->mapDependencies[$module][$type][$mapType] = array_merge_recursive( + $this->getDeclaredDependencies($module, $type, $mapType), + $dependencies + ); + } + + /** + * Converts a composer json component name into the Magento Module form. + * + * @param string $jsonName The name of a composer json component or dependency e.g. 'magento/module-theme' + * @return string The corresponding Magento Module e.g. 'Magento\Theme' + * @throws \Exception + */ + private function convertModuleName(string $jsonName): string + { + $moduleName = $this->getModuleName($jsonName); + if ($moduleName) { + return $moduleName; + } + + if (strpos($jsonName, 'magento/magento') !== false + || strpos($jsonName, 'magento/framework') !== false + ) { + $moduleName = str_replace('/', "\t", $jsonName); + $moduleName = str_replace('framework-', "Framework\t", $moduleName); + $moduleName = str_replace('-', ' ', $moduleName); + $moduleName = ucwords($moduleName); + $moduleName = str_replace("\t", '\\', $moduleName); + $moduleName = str_replace(' ', '', $moduleName); + } else { + $moduleName = $jsonName; + } + + return $moduleName; + } +} \ No newline at end of file diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index a4113abed8030..83dd58ce45bdd 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -10,8 +10,8 @@ use Magento\Framework\App\Utility\Files; use Magento\Framework\Component\ComponentRegistrar; +use Magento\Test\Integrity\Dependency\DeclarativeSchemaDependencyProvider; use Magento\TestFramework\Dependency\DbRule; -use Magento\TestFramework\Dependency\DeclarativeSchemaRule; use Magento\TestFramework\Dependency\DiRule; use Magento\TestFramework\Dependency\LayoutRule; use Magento\TestFramework\Dependency\PhpRule; @@ -25,19 +25,28 @@ class DependencyTest extends \PHPUnit\Framework\TestCase { /** - * Types of dependencies between modules + * Soft dependency between modules */ const TYPE_SOFT = 'soft'; + /** + * Hard dependency between modules + */ const TYPE_HARD = 'hard'; /** - * Types of dependencies map arrays + * The identifier of dependency for mapping. */ const MAP_TYPE_DECLARED = 'declared'; + /** + * The identifier of dependency for mapping. + */ const MAP_TYPE_FOUND = 'found'; + /** + * The identifier of dependency for mapping. + */ const MAP_TYPE_REDUNDANT = 'redundant'; /** @@ -56,17 +65,6 @@ class DependencyTest extends \PHPUnit\Framework\TestCase */ protected static $_listConfigXml = []; - /** - * List of db_schema.xml files by modules - * - * Format: array( - * '{Module_Name}' => '{Filename}' - * ) - * - * @var array - */ - protected static $_listDbSchemaXml = []; - /** * List of routes.xml files by modules * @@ -174,7 +172,6 @@ public static function setUpBeforeClass() self::$_namespaces = implode('|', Files::init()->getNamespaces()); self::_prepareListConfigXml(); - self::_prepareListDbSchemaXml(); self::_prepareListRoutesXml(); self::_prepareMapRouters(); @@ -230,7 +227,6 @@ protected static function _initRules() $dbRuleTables = array_merge($dbRuleTables, @include $fileName); } self::$_rulesInstances = [ - new DeclarativeSchemaRule($dbRuleTables), new PhpRule(self::$_mapRouters, self::$_mapLayoutBlocks), new DbRule($dbRuleTables), new LayoutRule( @@ -261,7 +257,6 @@ protected function _getCleanedFileContents($fileType, $file) break; case 'layout': case 'config': - case 'db_schema': //Removing xml comments $contents = preg_replace('~\~s', '', $contents); break; @@ -285,6 +280,9 @@ function ($matches) use ($contents, &$contentsWithoutHtml) { return $contents; } + /** + * @inheritdoc + */ public function testUndeclared() { $invoker = new \Magento\Framework\App\Utility\AggregateInvoker($this); @@ -416,17 +414,21 @@ private function collectDependency($dependency, $currentModule, &$undeclared) /** * Collect redundant dependencies + * * @SuppressWarnings(PHPMD.NPathComplexity) * @test * @depends testUndeclared + * @throws \Exception */ public function collectRedundant() { + $schemaDependencyProvider = new DeclarativeSchemaDependencyProvider(); foreach (array_keys(self::$mapDependencies) as $module) { $declared = $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_DECLARED); $found = array_merge( $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_FOUND), - $this->_getDependencies($module, self::TYPE_SOFT, self::MAP_TYPE_FOUND) + $this->_getDependencies($module, self::TYPE_SOFT, self::MAP_TYPE_FOUND), + $schemaDependencyProvider->getDeclaredExistingModuleDependencies($module) ); $found['Magento\Framework'] = 'Magento\Framework'; $this->_setDependencies($module, self::TYPE_HARD, self::MAP_TYPE_REDUNDANT, array_diff($declared, $found)); @@ -487,6 +489,7 @@ protected function _prepareFiles($fileType, $files, $skip = null) * Return all files * * @return array + * @throws \Exception */ public function getAllFiles() { @@ -508,12 +511,6 @@ public function getAllFiles() $this->_prepareFiles('config', Files::init()->getConfigFiles()) ); - // Get all configuration files - $files = array_merge( - $files, - $this->_prepareFiles('db_schema', Files::init()->getDbSchemaFiles()) - ); - //Get all layout updates files $files = array_merge( $files, @@ -543,20 +540,6 @@ protected static function _prepareListConfigXml() } } - /** - * Prepare list of db_schema.xml files (by modules) - */ - protected static function _prepareListDbSchemaXml() - { - $files = Files::init()->getDbSchemaFiles('db_schema.xml', [], false); - foreach ($files as $file) { - if (preg_match('/(?[A-Z][a-z]+)[_\/\\\\](?[A-Z][a-zA-Z]+)/', $file, $matches)) { - $module = $matches['namespace'] . '\\' . $matches['module']; - self::$_listDbSchemaXml[$module] = $file; - } - } - } - /** * Prepare list of routes.xml files (by modules) */ From 22bd9b2ab045067886855553d0393ee6c847b57d Mon Sep 17 00:00:00 2001 From: Max Lesechko Date: Mon, 12 Nov 2018 09:16:41 -0600 Subject: [PATCH 058/275] MC-5421: Create test to check dependencies between modules in Declarative Schema --- .../Dependency/DeclarativeSchemaRuleTest.php | 98 ------------------- 1 file changed, 98 deletions(-) delete mode 100644 dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/DeclarativeSchemaRuleTest.php diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/DeclarativeSchemaRuleTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/DeclarativeSchemaRuleTest.php deleted file mode 100644 index 269eff8087a91..0000000000000 --- a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Dependency/DeclarativeSchemaRuleTest.php +++ /dev/null @@ -1,98 +0,0 @@ -model = new DeclarativeSchemaRule(['some_table' => 'SomeModule']); - } - - /** - * @param string $module - * @param string $file - * @param string $contents - * @param array $expected - * @dataProvider getDependencyInfoDataProvider - */ - public function testGetDependencyInfo($module, $file, $contents, array $expected) - { - $actualDependencies = $this->model->getDependencyInfo($module, 'db_schema', $file, $contents); - $this->assertEquals( - $expected, - $actualDependencies - ); - } - - public function getDependencyInfoDataProvider() - { - return [ - ['any', 'non-db-schema-file.php', 'any', []], - [ - 'any', - '/app/Magento/Module/etc/db_schema.xml', - '
', - [['module' => 'Unknown', 'source' => 'unknown_table']] - ], - [ - 'SomeModule', - '/app/some/path/etc/db_schema.xml', - '
', - [] - ], - [ - 'any', - '/app/some/path/etc/db_schema.xml', - '
', - [ - [ - 'module' => 'SomeModule', - 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD, - 'source' => 'some_table', - ] - ] - ], - [ - 'any', - '/app/some/path/etc/db_schema.xml', - ' - - -
-
', - [ - [ - 'module' => 'SomeModule', - 'type' => \Magento\TestFramework\Dependency\RuleInterface::TYPE_HARD, - 'source' => 'some_table', - ], - [ - 'module' => 'Unknown', - 'source' => 'ref_table', - ] - ] - ] - ]; - } -} From e27f09f987f289b0a2d3f1e0552f67c63e2e302d Mon Sep 17 00:00:00 2001 From: Erik Pellikka Date: Sat, 17 Nov 2018 23:54:53 +0200 Subject: [PATCH 059/275] add missing fields to quote_address --- app/code/Magento/Quote/etc/db_schema.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Quote/etc/db_schema.xml b/app/code/Magento/Quote/etc/db_schema.xml index 7dd215b87e8cc..ec06077cbe43b 100644 --- a/app/code/Magento/Quote/etc/db_schema.xml +++ b/app/code/Magento/Quote/etc/db_schema.xml @@ -202,6 +202,8 @@ + + From 7f15bd86241bace94cdc77b5487bc7f593482a69 Mon Sep 17 00:00:00 2001 From: Erik Pellikka Date: Mon, 19 Nov 2018 17:49:14 +0200 Subject: [PATCH 060/275] add translations for the new columns --- app/code/Magento/Quote/i18n/en_US.csv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/code/Magento/Quote/i18n/en_US.csv b/app/code/Magento/Quote/i18n/en_US.csv index ae7453aa0d0cc..b24179297493a 100644 --- a/app/code/Magento/Quote/i18n/en_US.csv +++ b/app/code/Magento/Quote/i18n/en_US.csv @@ -65,3 +65,5 @@ error345,error345 Carts,Carts "Manage carts","Manage carts" "Invalid state change requested","Invalid state change requested" +"Validated Country Code","Validated Country Code" +"Validated Vat Number","Validated Vat Number" From 2c92422b0e5ff6336a4eadbfe3127acd73a7a8a5 Mon Sep 17 00:00:00 2001 From: Max Lesechko Date: Fri, 30 Nov 2018 11:21:04 -0600 Subject: [PATCH 061/275] MC-5421: Create test to check dependencies between modules in Declarative Schema --- .../TestFramework/Dependency/DiRule.php | 30 ++++++++++++++----- .../DeclarativeSchemaDependencyProvider.php | 4 +-- .../_files/dependency_test/tables_ce.php | 2 +- 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/dev/tests/static/framework/Magento/TestFramework/Dependency/DiRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/DiRule.php index cdaa49e8d37fb..8eda14dd28165 100644 --- a/dev/tests/static/framework/Magento/TestFramework/Dependency/DiRule.php +++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/DiRule.php @@ -7,11 +7,13 @@ */ namespace Magento\TestFramework\Dependency; -use DOMDocument; -use DOMXPath; +use Magento\Framework\App\Utility\Classes; use Magento\Framework\App\Utility\Files; use Magento\TestFramework\Dependency\VirtualType\VirtualTypeMapper; +/** + * Class provide dependency rule for di.xml config files. + */ class DiRule implements RuleInterface { /** @@ -33,6 +35,8 @@ public function __construct(VirtualTypeMapper $mapper) } /** + * Get class name pattern. + * * @return string * @throws \Exception */ @@ -99,12 +103,14 @@ public function getDependencyInfo($currentModule, $fileType, $file, &$contents) } /** + * Fetch all possible dependencies. + * * @param string $contents * @return array */ private function fetchPossibleDependencies($contents) { - $doc = new DOMDocument(); + $doc = new \DOMDocument(); $doc->loadXML($contents); return [ RuleInterface::TYPE_SOFT => $this->getSoftDependencies($doc), @@ -113,16 +119,22 @@ private function fetchPossibleDependencies($contents) } /** - * @param DOMDocument $doc + * Collect soft dependencies. + * + * @param \DOMDocument $doc * @return array */ - private function getSoftDependencies(DOMDocument $doc) + private function getSoftDependencies(\DOMDocument $doc) { $result = []; foreach (self::$tagNameMap as $tagName => $attributeNames) { $nodes = $doc->getElementsByTagName($tagName); /** @var \DOMElement $node */ foreach ($nodes as $node) { + if ($tagName === 'virtualType' && !$node->getAttribute('type')) { + $result[] = Classes::resolveVirtualType($node->getAttribute('name')); + continue; + } foreach ($attributeNames as $attributeName) { $result[] = $node->getAttribute($attributeName); } @@ -133,13 +145,15 @@ private function getSoftDependencies(DOMDocument $doc) } /** - * @param DOMDocument $doc + * Collect hard dependencies. + * + * @param \DOMDocument $doc * @return array */ - private function getHardDependencies(DOMDocument $doc) + private function getHardDependencies(\DOMDocument $doc) { $result = []; - $xpath = new DOMXPath($doc); + $xpath = new \DOMXPath($doc); $textNodes = $xpath->query('//*[@xsi:type="object"]'); /** @var \DOMElement $node */ foreach ($textNodes as $node) { diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php b/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php index b9050721c6910..f406291603757 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/Dependency/DeclarativeSchemaDependencyProvider.php @@ -92,7 +92,7 @@ public function getDeclaredExistingModuleDependencies(string $moduleName): array foreach ($dependencies as $dependency) { $checkResult = array_intersect($declared, $dependency); if ($checkResult) { - $existingDeclared = array_merge(array_values($checkResult)); + $existingDeclared = array_merge($existingDeclared, array_values($checkResult)); } } @@ -756,4 +756,4 @@ private function convertModuleName(string $jsonName): string return $moduleName; } -} \ No newline at end of file +} diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php index 530f55504d009..3fb53be2ec400 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/tables_ce.php @@ -96,7 +96,7 @@ 'catalogrule_website' => 'Magento\CatalogRule', 'catalogsearch_fulltext' => 'Magento\CatalogSearch', 'catalogsearch_result' => 'Magento\CatalogSearch', - 'search_query' => 'Magento\CatalogSearch', + 'search_query' => 'Magento\Search', 'checkout_agreement' => 'Magento\Checkout', 'checkout_agreement_store' => 'Magento\Checkout', 'cms_block' => 'Magento\Cms', From 8485985b61ac4633943fe6911f4af68f90cce05f Mon Sep 17 00:00:00 2001 From: Max Lesechko Date: Sun, 2 Dec 2018 01:29:42 -0600 Subject: [PATCH 062/275] MC-5421: Create test to check dependencies between modules in Declarative Schema --- .../Magento/Test/Integrity/DependencyTest.php | 15 ++++++++++++++- .../undetected_dependencies_ce.php | 10 ++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/undetected_dependencies_ce.php diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php index 83dd58ce45bdd..3b1e4aecedb54 100644 --- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php @@ -221,7 +221,7 @@ protected static function _initThemes() */ protected static function _initRules() { - $replaceFilePattern = str_replace('\\', '/', realpath(__DIR__)) . '/_files/dependency_test/*.php'; + $replaceFilePattern = str_replace('\\', '/', realpath(__DIR__)) . '/_files/dependency_test/tables_*.php'; $dbRuleTables = []; foreach (glob($replaceFilePattern) as $fileName) { $dbRuleTables = array_merge($dbRuleTables, @include $fileName); @@ -423,6 +423,14 @@ private function collectDependency($dependency, $currentModule, &$undeclared) public function collectRedundant() { $schemaDependencyProvider = new DeclarativeSchemaDependencyProvider(); + + /** TODO: Remove this temporary solution after MC-5806 is closed */ + $filePattern = __DIR__ . '/_files/dependency_test/undetected_dependencies_*.php'; + $undetectedDependencies = []; + foreach (glob($filePattern) as $fileName) { + $undetectedDependencies = array_merge($undetectedDependencies, require $fileName); + } + foreach (array_keys(self::$mapDependencies) as $module) { $declared = $this->_getDependencies($module, self::TYPE_HARD, self::MAP_TYPE_DECLARED); $found = array_merge( @@ -430,6 +438,11 @@ public function collectRedundant() $this->_getDependencies($module, self::TYPE_SOFT, self::MAP_TYPE_FOUND), $schemaDependencyProvider->getDeclaredExistingModuleDependencies($module) ); + /** TODO: Remove this temporary solution after MC-5806 is closed */ + if (!empty($undetectedDependencies[$module])) { + $found = array_merge($found, $undetectedDependencies[$module]); + } + $found['Magento\Framework'] = 'Magento\Framework'; $this->_setDependencies($module, self::TYPE_HARD, self::MAP_TYPE_REDUNDANT, array_diff($declared, $found)); } diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/undetected_dependencies_ce.php b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/undetected_dependencies_ce.php new file mode 100644 index 0000000000000..407f57ee51257 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Integrity/_files/dependency_test/undetected_dependencies_ce.php @@ -0,0 +1,10 @@ + ["Magento\CatalogSearch"] +]; From b5115c2340d8ae9b71851c795b1529027d9fc402 Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 4 Dec 2018 12:30:52 +0200 Subject: [PATCH 063/275] MAGETWO-96505: Fixed incorrect stacktrace displaying --- .../NewRelicReporting/Model/NewRelicWrapper.php | 6 +++++- app/code/Magento/User/Model/User.php | 13 ++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php index ec21e06976b8b..1407a64e0f660 100644 --- a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php +++ b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php @@ -37,7 +37,11 @@ public function addCustomParameter($param, $value) public function reportError($exception) { if (extension_loaded('newrelic')) { - newrelic_notice_error($exception->getMessage(), $exception); + if ($exception instanceof \Zend_Db_Statement_Exception || $exception instanceof \PDOException) { + newrelic_notice_error($exception->getMessage()); + } else { + newrelic_notice_error($exception->getMessage(), $exception); + } } } diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index 4050d79e593ee..d7aa9d76546fd 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -7,6 +7,7 @@ namespace Magento\User\Model; use Magento\Backend\Model\Auth\Credential\StorageInterface; +use Magento\Checkout\Exception; use Magento\Framework\App\ObjectManager; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Exception\AuthenticationException; @@ -629,8 +630,18 @@ public function verifyIdentity($password) public function login($username, $password) { if ($this->authenticate($username, $password)) { - $this->getResource()->recordLogin($this); + try { + $this->getResource()->recordLogin($this); + } catch (\Exception $exception) { + if ($exception instanceof \Zend_Db_Statement_Exception + || $exception instanceof \PDOException) { + $this->_logger->critical($exception); + + throw new \Exception($exception->getMessage()); + } + } } + return $this; } From 03d6b01998af379e7449ba07fe51547117fe2d49 Mon Sep 17 00:00:00 2001 From: Oleksandr Miroshnichenko Date: Fri, 7 Dec 2018 14:47:19 -0600 Subject: [PATCH 064/275] MC-5899: Incorrect Rss Wishlist response --- .../Magento/Wishlist/Model/Rss/Wishlist.php | 14 ++- .../Magento/Rss/Controller/Feed/IndexTest.php | 89 +++++++++++++++++++ .../two_wishlists_for_two_diff_customers.php | 25 ++++++ ...hlists_for_two_diff_customers_rollback.php | 19 ++++ 4 files changed, 139 insertions(+), 8 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Rss/Controller/Feed/IndexTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/two_wishlists_for_two_diff_customers.php create mode 100644 dev/tests/integration/testsuite/Magento/Wishlist/_files/two_wishlists_for_two_diff_customers_rollback.php diff --git a/app/code/Magento/Wishlist/Model/Rss/Wishlist.php b/app/code/Magento/Wishlist/Model/Rss/Wishlist.php index 9ccbf80f99a0c..ff59e0cdd7b91 100644 --- a/app/code/Magento/Wishlist/Model/Rss/Wishlist.php +++ b/app/code/Magento/Wishlist/Model/Rss/Wishlist.php @@ -118,10 +118,8 @@ public function __construct( */ public function isAllowed() { - return $this->scopeConfig->isSetFlag( - 'rss/wishlist/active', - ScopeInterface::SCOPE_STORE - ); + return $this->scopeConfig->isSetFlag('rss/wishlist/active', ScopeInterface::SCOPE_STORE) + && $this->getWishlist()->getCustomerId() === $this->wishlistHelper->getCustomer()->getId(); } /** @@ -185,8 +183,8 @@ public function getRssData() } } else { $data = [ - 'title' => __('We cannot retrieve the Wish List.'), - 'description' => __('We cannot retrieve the Wish List.'), + 'title' => __('We cannot retrieve the Wish List.')->render(), + 'description' => __('We cannot retrieve the Wish List.')->render(), 'link' => $this->urlBuilder->getUrl(), 'charset' => 'UTF-8', ]; @@ -202,7 +200,7 @@ public function getRssData() */ public function getCacheKey() { - return 'rss_wishlist_data'; + return 'rss_wishlist_data_' . $this->getWishlist()->getId(); } /** @@ -224,7 +222,7 @@ public function getHeader() { $customerId = $this->getWishlist()->getCustomerId(); $customer = $this->customerFactory->create()->load($customerId); - $title = __('%1\'s Wishlist', $customer->getName()); + $title = __('%1\'s Wishlist', $customer->getName())->render(); $newUrl = $this->urlBuilder->getUrl( 'wishlist/shared/index', ['code' => $this->getWishlist()->getSharingCode()] diff --git a/dev/tests/integration/testsuite/Magento/Rss/Controller/Feed/IndexTest.php b/dev/tests/integration/testsuite/Magento/Rss/Controller/Feed/IndexTest.php new file mode 100644 index 0000000000000..26d989d655ade --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Rss/Controller/Feed/IndexTest.php @@ -0,0 +1,89 @@ +urlBuilder = $this->_objectManager->get(\Magento\Rss\Model\UrlBuilder::class); + $this->customerRepository = $this->_objectManager->get(\Magento\Customer\Api\CustomerRepositoryInterface::class); + $this->wishlist = $this->_objectManager->get(\Magento\Wishlist\Model\Wishlist::class); + $this->customerSession = $this->_objectManager->get(\Magento\Customer\Model\Session::class); + } + + /** + * Check Rss response. + * + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Wishlist/_files/two_wishlists_for_two_diff_customers.php + * @magentoConfigFixture current_store rss/wishlist/active 1 + * @magentoConfigFixture current_store rss/config/active 1 + */ + public function testRssResponse() + { + $firstCustomerId = 1; + $this->customerSession->setCustomerId($firstCustomerId); + $customer = $this->customerRepository->getById($firstCustomerId); + $customerEmail = $customer->getEmail(); + $wishlistId = $this->wishlist->loadByCustomerId($firstCustomerId)->getId(); + $this->dispatch($this->getLink($firstCustomerId, $customerEmail, $wishlistId)); + $body = $this->getResponse()->getBody(); + $this->assertContains('John Smith\'s Wishlist', $body); + } + + /** + * Check Rss with incorrect wishlist id. + * + * @magentoAppIsolation enabled + * @magentoDataFixture Magento/Wishlist/_files/two_wishlists_for_two_diff_customers.php + * @magentoConfigFixture current_store rss/wishlist/active 1 + * @magentoConfigFixture current_store rss/config/active 1 + */ + public function testRssResponseWithIncorrectWishlistId() + { + $firstCustomerId = 1; + $secondCustomerId = 2; + $this->customerSession->setCustomerId($firstCustomerId); + $customer = $this->customerRepository->getById($firstCustomerId); + $customerEmail = $customer->getEmail(); + $wishlistId = $this->wishlist->loadByCustomerId($secondCustomerId, true)->getId(); + $this->dispatch($this->getLink($firstCustomerId, $customerEmail, $wishlistId)); + $body = $this->getResponse()->getBody(); + $this->assertContains('404 Not Found', $body); + } + + private function getLink($customerId, $customerEmail, $wishlistId) + { + + return 'rss/feed/index/type/wishlist/data/' + . base64_encode($customerId . ',' . $customerEmail) + . '/wishlist_id/' . $wishlistId; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/two_wishlists_for_two_diff_customers.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/two_wishlists_for_two_diff_customers.php new file mode 100644 index 0000000000000..a71c9c9ba3c6f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/two_wishlists_for_two_diff_customers.php @@ -0,0 +1,25 @@ +create( + \Magento\Wishlist\Model\Wishlist::class +); +$wishlistForFirstCustomer->loadByCustomerId($firstCustomerIdFromFixture, true); +$item = $wishlistForFirstCustomer->addNewItem($product, new \Magento\Framework\DataObject([])); +$wishlistForFirstCustomer->save(); + +$secondCustomerIdFromFixture = 2; +$wishlistForSecondCustomer = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create( + \Magento\Wishlist\Model\Wishlist::class +); +$wishlistForSecondCustomer->loadByCustomerId($secondCustomerIdFromFixture, true); +$item = $wishlistForSecondCustomer->addNewItem($product, new \Magento\Framework\DataObject([])); +$wishlistForSecondCustomer->save(); diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/_files/two_wishlists_for_two_diff_customers_rollback.php b/dev/tests/integration/testsuite/Magento/Wishlist/_files/two_wishlists_for_two_diff_customers_rollback.php new file mode 100644 index 0000000000000..4baba3b9f77c8 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Wishlist/_files/two_wishlists_for_two_diff_customers_rollback.php @@ -0,0 +1,19 @@ +create(\Magento\Wishlist\Model\Wishlist::class); +$wishlist->loadByCustomerId(1); +$wishlist->delete(); +$wishlist->loadByCustomerId(2); +$wishlist->delete(); + +require __DIR__ . '/../../../Magento/Customer/_files/two_customers_rollback.php'; +require __DIR__ . '/../../../Magento/Catalog/_files/product_simple_rollback.php'; From db29b1f92d434f6346baf53490129cf4e516e5c7 Mon Sep 17 00:00:00 2001 From: Oleksandr Miroshnichenko Date: Mon, 10 Dec 2018 09:44:01 -0600 Subject: [PATCH 065/275] MC-5899: Incorrect Rss Wishlist response - fix tests --- .../Test/Unit/Model/Rss/WishlistTest.php | 22 ++++++++++++++++++- .../Magento/Rss/Controller/Feed/IndexTest.php | 4 +++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php index 85f6c504457d3..fc43baa0a67de 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Model/Rss/WishlistTest.php @@ -278,15 +278,35 @@ protected function processWishlistItemDescription($wishlistModelMock, $staticArg public function testIsAllowed() { + $customerId = 1; + $customerServiceMock = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); + $wishlist = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class)->setMethods( + ['getId', '__wakeup', 'getCustomerId', 'getItemCollection', 'getSharingCode'] + )->disableOriginalConstructor()->getMock(); + $wishlist->expects($this->once())->method('getCustomerId')->willReturn($customerId); + $this->wishlistHelperMock->expects($this->any())->method('getWishlist') + ->will($this->returnValue($wishlist)); + $this->wishlistHelperMock->expects($this->any()) + ->method('getCustomer') + ->will($this->returnValue($customerServiceMock)); + $customerServiceMock->expects($this->once())->method('getId')->willReturn($customerId); $this->scopeConfig->expects($this->once())->method('isSetFlag') ->with('rss/wishlist/active', \Magento\Store\Model\ScopeInterface::SCOPE_STORE) ->will($this->returnValue(true)); + $this->assertTrue($this->model->isAllowed()); } public function testGetCacheKey() { - $this->assertEquals('rss_wishlist_data', $this->model->getCacheKey()); + $wishlistId = 1; + $wishlist = $this->getMockBuilder(\Magento\Wishlist\Model\Wishlist::class)->setMethods( + ['getId', '__wakeup', 'getCustomerId', 'getItemCollection', 'getSharingCode'] + )->disableOriginalConstructor()->getMock(); + $wishlist->expects($this->once())->method('getId')->willReturn($wishlistId); + $this->wishlistHelperMock->expects($this->any())->method('getWishlist') + ->will($this->returnValue($wishlist)); + $this->assertEquals('rss_wishlist_data_1', $this->model->getCacheKey()); } public function testGetCacheLifetime() diff --git a/dev/tests/integration/testsuite/Magento/Rss/Controller/Feed/IndexTest.php b/dev/tests/integration/testsuite/Magento/Rss/Controller/Feed/IndexTest.php index 26d989d655ade..9a611b8f2b9ea 100644 --- a/dev/tests/integration/testsuite/Magento/Rss/Controller/Feed/IndexTest.php +++ b/dev/tests/integration/testsuite/Magento/Rss/Controller/Feed/IndexTest.php @@ -33,7 +33,9 @@ protected function setUp() { parent::setUp(); $this->urlBuilder = $this->_objectManager->get(\Magento\Rss\Model\UrlBuilder::class); - $this->customerRepository = $this->_objectManager->get(\Magento\Customer\Api\CustomerRepositoryInterface::class); + $this->customerRepository = $this->_objectManager->get( + \Magento\Customer\Api\CustomerRepositoryInterface::class + ); $this->wishlist = $this->_objectManager->get(\Magento\Wishlist\Model\Wishlist::class); $this->customerSession = $this->_objectManager->get(\Magento\Customer\Model\Session::class); } From 2c4173e2dbe331ac7f97163083efd652cb3fc0bb Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 18 Dec 2018 13:05:14 +0200 Subject: [PATCH 066/275] MAGETWO-96757: Fixed incorrect displaying of the sales rule conditions --- app/code/Magento/Rule/Block/Editable.php | 6 ++++-- .../Adminhtml/Promo/Quote/NewConditionHtml.php | 2 +- .../Data/Form/Element/AbstractElement.php | 14 +++++++++++--- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Rule/Block/Editable.php b/app/code/Magento/Rule/Block/Editable.php index 67e4671236ea0..d53213a7df876 100644 --- a/app/code/Magento/Rule/Block/Editable.php +++ b/app/code/Magento/Rule/Block/Editable.php @@ -9,6 +9,8 @@ use Magento\Framework\View\Element\AbstractBlock; /** + * Renderer for Editable sales rules + * * @api * @since 100.0.2 */ @@ -52,9 +54,9 @@ public function render(\Magento\Framework\Data\Form\Element\AbstractElement $ele if ($element->getShowAsText()) { $html = ' getName() . ($suffix ?: '') . '"'; + return ' data-ui-id="form-element-' . $this->_escaper->escapeHtml($this->getName()) . ($suffix ?: '') . '"'; } } From 36f81368ec1e452cbd5910231f517265666e27a1 Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 18 Dec 2018 16:47:45 +0200 Subject: [PATCH 067/275] MC-5964: Fixed incorrect behaviour of sync actions --- .../ProductFrontendAction/Synchronizer.php | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php index 3ec8e968aa245..331c6673a555c 100644 --- a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php +++ b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php @@ -138,7 +138,9 @@ private function getProductIdsByActions(array $actions) $productIds = []; foreach ($actions as $action) { - $productIds[] = $action['product_id']; + if (isset($action['product_id']) && is_int($action['product_id'])) { + $productIds[] = $action['product_id']; + } } return $productIds; @@ -159,33 +161,37 @@ public function syncActions(array $productsData, $typeId) $customerId = $this->session->getCustomerId(); $visitorId = $this->visitor->getId(); $collection = $this->getActionsByType($typeId); - $collection->addFieldToFilter('product_id', $this->getProductIdsByActions($productsData)); - - /** - * Note that collection is also filtered by visitor id and customer id - * This collection shouldn't be flushed when visitor has products and then login - * It can remove only products for visitor, or only products for customer - * - * ['product_id' => 'added_at'] - * @var ProductFrontendActionInterface $item - */ - foreach ($collection as $item) { - $this->entityManager->delete($item); - } - - foreach ($productsData as $productId => $productData) { - /** @var ProductFrontendActionInterface $action */ - $action = $this->productFrontendActionFactory->create([ - 'data' => [ - 'visitor_id' => $customerId ? null : $visitorId, - 'customer_id' => $this->session->getCustomerId(), - 'added_at' => $productData['added_at'], - 'product_id' => $productId, - 'type_id' => $typeId - ] - ]); - - $this->entityManager->save($action); + $productIds = $this->getProductIdsByActions($productsData); + + if ($productIds) { + $collection->addFieldToFilter('product_id', $productIds); + + /** + * Note that collection is also filtered by visitor id and customer id + * This collection shouldn't be flushed when visitor has products and then login + * It can remove only products for visitor, or only products for customer + * + * ['product_id' => 'added_at'] + * @var ProductFrontendActionInterface $item + */ + foreach ($collection as $item) { + $this->entityManager->delete($item); + } + + foreach ($productsData as $productId => $productData) { + /** @var ProductFrontendActionInterface $action */ + $action = $this->productFrontendActionFactory->create([ + 'data' => [ + 'visitor_id' => $customerId ? null : $visitorId, + 'customer_id' => $this->session->getCustomerId(), + 'added_at' => $productData['added_at'], + 'product_id' => $productId, + 'type_id' => $typeId + ] + ]); + + $this->entityManager->save($action); + } } } From 648dcf43278863acf5c029bbd7cb94e569ea54e6 Mon Sep 17 00:00:00 2001 From: Erfan Date: Wed, 19 Dec 2018 16:36:15 +0800 Subject: [PATCH 068/275] Added custom_options file upload directory to .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 68d38d9ca7817..8a15fbacaf638 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ atlassian* /pub/media/import/* !/pub/media/import/.htaccess /pub/media/logo/* +/pub/media/custom_options/* /pub/media/theme/* /pub/media/theme_customization/* !/pub/media/theme_customization/.htaccess From 427ba17a4b334f5f5d8c0d9102d82ae774e87923 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 20 Dec 2018 12:58:09 +0200 Subject: [PATCH 069/275] MC-5964: Fixed incorrect behaviour of sync actions --- lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index 86266ec23fe47..c2693d9d0fb8d 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -2965,7 +2965,7 @@ public function prepareSqlCondition($fieldName, $condition) if (isset($condition['to'])) { $query .= empty($query) ? '' : ' AND '; $to = $this->_prepareSqlDateCondition($condition, 'to'); - $query = $this->_prepareQuotedSqlCondition($query . $conditionKeyMap['to'], $to, $fieldName); + $query = $this->_prepareQuotedSqlCondition($conditionKeyMap['to'], $to, $fieldName); } } elseif (array_key_exists($key, $conditionKeyMap)) { $value = $condition[$key]; From 60ae26d9fbff50e247b9803e0a247bfd51f4332a Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 20 Dec 2018 15:52:56 +0200 Subject: [PATCH 070/275] MC-5964: Fixed incorrect behaviour of sync actions --- lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php index c2693d9d0fb8d..fc656987963fb 100644 --- a/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php +++ b/lib/internal/Magento/Framework/DB/Adapter/Pdo/Mysql.php @@ -2965,7 +2965,7 @@ public function prepareSqlCondition($fieldName, $condition) if (isset($condition['to'])) { $query .= empty($query) ? '' : ' AND '; $to = $this->_prepareSqlDateCondition($condition, 'to'); - $query = $this->_prepareQuotedSqlCondition($conditionKeyMap['to'], $to, $fieldName); + $query = $query . $this->_prepareQuotedSqlCondition($conditionKeyMap['to'], $to, $fieldName); } } elseif (array_key_exists($key, $conditionKeyMap)) { $value = $condition[$key]; From 90b32ae7fb5fcfafb9f2f2c702a1b9615c6586d2 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 27 Dec 2018 13:14:47 +0200 Subject: [PATCH 071/275] MAGETWO-96505: Fixed incorrect stacktrace displaying --- lib/internal/Magento/Framework/App/Http.php | 17 +++++++++++---- .../Magento/Framework/App/StaticResource.php | 13 ++++++++++-- .../Magento/Framework/Message/Manager.php | 21 ++++++++++++++----- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/lib/internal/Magento/Framework/App/Http.php b/lib/internal/Magento/Framework/App/Http.php index 3c6dee49f97b4..96d34b93fa741 100644 --- a/lib/internal/Magento/Framework/App/Http.php +++ b/lib/internal/Magento/Framework/App/Http.php @@ -6,6 +6,7 @@ namespace Magento\Framework\App; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Debug; use Magento\Framework\ObjectManager\ConfigLoaderInterface; use Magento\Framework\App\Request\Http as RequestHttp; use Magento\Framework\App\Response\Http as ResponseHttp; @@ -79,7 +80,7 @@ class Http implements \Magento\Framework\AppInterface * @param ResponseHttp $response * @param ConfigLoaderInterface $configLoader * @param State $state - * @param Filesystem $filesystem, + * @param Filesystem $filesystem * @param \Magento\Framework\Registry $registry */ public function __construct( @@ -149,7 +150,7 @@ public function launch() } /** - * {@inheritdoc} + * @inheritdoc */ public function catchException(Bootstrap $bootstrap, \Exception $exception) { @@ -214,7 +215,7 @@ private function buildContentFromException(\Exception $exception) $index, get_class($exception), $exception->getMessage(), - $exception->getTraceAsString() + Debug::trace($exception->getTrace(), true, true, false) ); } @@ -312,7 +313,15 @@ private function handleInitException(\Exception $exception) */ private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception) { - $reportData = [$exception->getMessage(), $exception->getTraceAsString()]; + $reportData = [ + $exception->getMessage(), + Debug::trace( + $exception->getTrace(), + true, + true, + (bool)DEBUG_BACKTRACE_IGNORE_ARGS + ) + ]; $params = $bootstrap->getParams(); if (isset($params['REQUEST_URI'])) { $reportData['url'] = $params['REQUEST_URI']; diff --git a/lib/internal/Magento/Framework/App/StaticResource.php b/lib/internal/Magento/Framework/App/StaticResource.php index 575074fdb58ac..6a753fee6eaf9 100644 --- a/lib/internal/Magento/Framework/App/StaticResource.php +++ b/lib/internal/Magento/Framework/App/StaticResource.php @@ -10,6 +10,7 @@ use Magento\Framework\Filesystem; use Magento\Framework\Config\ConfigOptionsListConstants; use Psr\Log\LoggerInterface; +use Magento\Framework\Debug; /** * Entry point for retrieving static resources like JS, CSS, images by requested public path @@ -138,7 +139,7 @@ public function launch() } /** - * {@inheritdoc} + * @inheritdoc */ public function catchException(Bootstrap $bootstrap, \Exception $exception) { @@ -146,7 +147,15 @@ public function catchException(Bootstrap $bootstrap, \Exception $exception) if ($bootstrap->isDeveloperMode()) { $this->response->setHttpResponseCode(404); $this->response->setHeader('Content-Type', 'text/plain'); - $this->response->setBody($exception->getMessage() . "\n" . $exception->getTraceAsString()); + $this->response->setBody( + $exception->getMessage() . "\n" . + Debug::trace( + $exception->getTrace(), + true, + true, + false + ) + ); $this->response->sendResponse(); } else { require $this->getFilesystem()->getDirectoryRead(DirectoryList::PUB)->getAbsolutePath('errors/404.php'); diff --git a/lib/internal/Magento/Framework/Message/Manager.php b/lib/internal/Magento/Framework/Message/Manager.php index f31892a938fb1..1b39a777063ed 100644 --- a/lib/internal/Magento/Framework/Message/Manager.php +++ b/lib/internal/Magento/Framework/Message/Manager.php @@ -8,6 +8,7 @@ use Magento\Framework\Event; use Psr\Log\LoggerInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\Debug; /** * Message manager model @@ -67,7 +68,7 @@ class Manager implements ManagerInterface * @param Event\ManagerInterface $eventManager * @param LoggerInterface $logger * @param string $defaultGroup - * @param ExceptionMessageFactoryInterface|null exceptionMessageFactory + * @param ExceptionMessageFactoryInterface|null $exceptionMessageFactory */ public function __construct( Session $session, @@ -89,7 +90,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getDefaultGroup() { @@ -110,8 +111,8 @@ protected function prepareGroup($group) /** * @inheritdoc * - * @param string|null $group * @param bool $clear + * @param string|null $group * @return Collection */ public function getMessages($clear = false, $group = null) @@ -248,7 +249,12 @@ public function addException(\Exception $exception, $alternativeText = null, $gr 'Exception message: %s%sTrace: %s', $exception->getMessage(), "\n", - $exception->getTraceAsString() + Debug::trace( + $exception->getTrace(), + true, + true, + false + ) ); $this->logger->critical($message); @@ -286,7 +292,12 @@ public function addExceptionMessage(\Exception $exception, $alternativeText = nu 'Exception message: %s%sTrace: %s', $exception->getMessage(), "\n", - $exception->getTraceAsString() + Debug::trace( + $exception->getTrace(), + true, + true, + false + ) ); $this->logger->critical($message); From fd92e76855d60c05867071c3b98e6dc285e6c0de Mon Sep 17 00:00:00 2001 From: roman Date: Fri, 4 Jan 2019 16:11:38 +0200 Subject: [PATCH 072/275] MAGETWO-96505: Fixed incorrect stacktrace displaying --- .../NewRelicReporting/Model/NewRelicWrapper.php | 6 +----- app/code/Magento/User/Model/User.php | 13 +------------ 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php index 1407a64e0f660..ec21e06976b8b 100644 --- a/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php +++ b/app/code/Magento/NewRelicReporting/Model/NewRelicWrapper.php @@ -37,11 +37,7 @@ public function addCustomParameter($param, $value) public function reportError($exception) { if (extension_loaded('newrelic')) { - if ($exception instanceof \Zend_Db_Statement_Exception || $exception instanceof \PDOException) { - newrelic_notice_error($exception->getMessage()); - } else { - newrelic_notice_error($exception->getMessage(), $exception); - } + newrelic_notice_error($exception->getMessage(), $exception); } } diff --git a/app/code/Magento/User/Model/User.php b/app/code/Magento/User/Model/User.php index d7aa9d76546fd..4050d79e593ee 100644 --- a/app/code/Magento/User/Model/User.php +++ b/app/code/Magento/User/Model/User.php @@ -7,7 +7,6 @@ namespace Magento\User\Model; use Magento\Backend\Model\Auth\Credential\StorageInterface; -use Magento\Checkout\Exception; use Magento\Framework\App\ObjectManager; use Magento\Framework\Model\AbstractModel; use Magento\Framework\Exception\AuthenticationException; @@ -630,18 +629,8 @@ public function verifyIdentity($password) public function login($username, $password) { if ($this->authenticate($username, $password)) { - try { - $this->getResource()->recordLogin($this); - } catch (\Exception $exception) { - if ($exception instanceof \Zend_Db_Statement_Exception - || $exception instanceof \PDOException) { - $this->_logger->critical($exception); - - throw new \Exception($exception->getMessage()); - } - } + $this->getResource()->recordLogin($this); } - return $this; } From 3ee104d59b0dbe9f843bcca162ce24cef6ac09fa Mon Sep 17 00:00:00 2001 From: Jeroen van Leusden Date: Wed, 12 Dec 2018 15:29:34 +0100 Subject: [PATCH 073/275] Use repository to load order when manually creating an invoice ENGCOM-3679: Static test fix. ENGCOM-3679: Unit test fix. --- .../Adminhtml/Order/Invoice/NewAction.php | 28 +++++++---- .../Adminhtml/Order/Invoice/NewActionTest.php | 47 +++++++++---------- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php index 3295b244f323e..ceb231248ef5e 100644 --- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php +++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php @@ -1,18 +1,21 @@ registry = $registry; $this->resultPageFactory = $resultPageFactory; - parent::__construct($context); $this->invoiceService = $invoiceService; + $this->orderRepository = $orderRepository ?: \Magento\Framework\App\ObjectManager::getInstance() + ->get(OrderRepositoryInterface::class); } /** @@ -78,14 +91,11 @@ public function execute() { $orderId = $this->getRequest()->getParam('order_id'); $invoiceData = $this->getRequest()->getParam('invoice', []); - $invoiceItems = isset($invoiceData['items']) ? $invoiceData['items'] : []; + $invoiceItems = $invoiceData['items'] ?? []; try { /** @var \Magento\Sales\Model\Order $order */ - $order = $this->_objectManager->create(\Magento\Sales\Model\Order::class)->load($orderId); - if (!$order->getId()) { - throw new \Magento\Framework\Exception\LocalizedException(__('The order no longer exists.')); - } + $order = $this->orderRepository->get($orderId); if (!$order->canInvoice()) { throw new \Magento\Framework\Exception\LocalizedException( diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php index 05c99c9f9ef98..87e27fdb2206b 100644 --- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php +++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php @@ -6,12 +6,14 @@ namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order\Invoice; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +use Magento\Sales\Api\OrderRepositoryInterface; /** * Class NewActionTest * @package Magento\Sales\Controller\Adminhtml\Order\Invoice * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.TooManyFields) */ class NewActionTest extends \PHPUnit\Framework\TestCase { @@ -90,6 +92,11 @@ class NewActionTest extends \PHPUnit\Framework\TestCase */ protected $invoiceServiceMock; + /** + * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject + */ + private $orderRepositoryMock; + protected function setUp() { $objectManager = new ObjectManager($this); @@ -215,12 +222,15 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class); + $this->controller = $objectManager->getObject( \Magento\Sales\Controller\Adminhtml\Order\Invoice\NewAction::class, [ 'context' => $contextMock, 'resultPageFactory' => $this->resultPageFactoryMock, - 'invoiceService' => $this->invoiceServiceMock + 'invoiceService' => $this->invoiceServiceMock, + 'orderRepository' => $this->orderRepositoryMock ] ); } @@ -250,19 +260,17 @@ public function testExecute() $orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->disableOriginalConstructor() - ->setMethods(['load', 'getId', 'canInvoice']) + ->setMethods(['load', 'canInvoice']) ->getMock(); - $orderMock->expects($this->once()) - ->method('load') - ->with($orderId) - ->willReturnSelf(); - $orderMock->expects($this->once()) - ->method('getId') - ->willReturn($orderId); $orderMock->expects($this->once()) ->method('canInvoice') ->willReturn(true); + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderId) + ->willReturn($orderMock); + $this->invoiceServiceMock->expects($this->once()) ->method('prepareInvoice') ->with($orderMock, []) @@ -285,11 +293,7 @@ public function testExecute() ->with(true) ->will($this->returnValue($commentText)); - $this->objectManagerMock->expects($this->at(0)) - ->method('create') - ->with(\Magento\Sales\Model\Order::class) - ->willReturn($orderMock); - $this->objectManagerMock->expects($this->at(1)) + $this->objectManagerMock->expects($this->once()) ->method('get') ->with(\Magento\Backend\Model\Session::class) ->will($this->returnValue($this->sessionMock)); @@ -318,19 +322,12 @@ public function testExecuteNoOrder() $orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class) ->disableOriginalConstructor() - ->setMethods(['load', 'getId', 'canInvoice']) + ->setMethods(['canInvoice']) ->getMock(); - $orderMock->expects($this->once()) - ->method('load') - ->with($orderId) - ->willReturnSelf(); - $orderMock->expects($this->once()) - ->method('getId') - ->willReturn(null); - $this->objectManagerMock->expects($this->at(0)) - ->method('create') - ->with(\Magento\Sales\Model\Order::class) + $this->orderRepositoryMock->expects($this->once()) + ->method('get') + ->with($orderId) ->willReturn($orderMock); $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class) From ceb1edd4307a6d7e1b9622bcfa85403de3e7213f Mon Sep 17 00:00:00 2001 From: roman Date: Tue, 8 Jan 2019 14:37:08 +0200 Subject: [PATCH 074/275] MAGETWO-96505: Fixed incorrect stacktrace displaying --- lib/internal/Magento/Framework/App/Http.php | 20 +++++++++++++++++-- .../Magento/Framework/App/StaticResource.php | 14 +++++++++++-- .../Magento/Framework/Message/Manager.php | 13 ++++++++++-- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/lib/internal/Magento/Framework/App/Http.php b/lib/internal/Magento/Framework/App/Http.php index 96d34b93fa741..1b9db1d4069be 100644 --- a/lib/internal/Magento/Framework/App/Http.php +++ b/lib/internal/Magento/Framework/App/Http.php @@ -14,6 +14,7 @@ use Magento\Framework\Controller\ResultInterface; use Magento\Framework\Event; use Magento\Framework\Filesystem; +use Zend\Http\PhpEnvironment\Request as Environment; /** * HTTP web application. Called from webroot index.php to serve web requests. @@ -72,6 +73,11 @@ class Http implements \Magento\Framework\AppInterface */ private $logger; + /** + * @var Environment + */ + private $env; + /** * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param Event\Manager $eventManager @@ -82,6 +88,8 @@ class Http implements \Magento\Framework\AppInterface * @param State $state * @param Filesystem $filesystem * @param \Magento\Framework\Registry $registry + * @param Environment|null $env + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, @@ -92,7 +100,8 @@ public function __construct( ConfigLoaderInterface $configLoader, State $state, Filesystem $filesystem, - \Magento\Framework\Registry $registry + \Magento\Framework\Registry $registry, + ?Environment $env = null ) { $this->_objectManager = $objectManager; $this->_eventManager = $eventManager; @@ -103,6 +112,7 @@ public function __construct( $this->_state = $state; $this->_filesystem = $filesystem; $this->registry = $registry; + $this->env = $env ?: ObjectManager::getInstance()->create(Environment::class); } /** @@ -199,6 +209,7 @@ private function buildContentFromException(\Exception $exception) { /** @var \Exception[] $exceptions */ $exceptions = []; + do { $exceptions[] = $exception; } while ($exception = $exception->getPrevious()); @@ -215,7 +226,12 @@ private function buildContentFromException(\Exception $exception) $index, get_class($exception), $exception->getMessage(), - Debug::trace($exception->getTrace(), true, true, false) + Debug::trace( + $exception->getTrace(), + true, + true, + boolval($this->env->getEnv('DEBUG_SHOW_ARGS', true)) + ) ); } diff --git a/lib/internal/Magento/Framework/App/StaticResource.php b/lib/internal/Magento/Framework/App/StaticResource.php index 6a753fee6eaf9..bbb63d3dbe580 100644 --- a/lib/internal/Magento/Framework/App/StaticResource.php +++ b/lib/internal/Magento/Framework/App/StaticResource.php @@ -11,6 +11,7 @@ use Magento\Framework\Config\ConfigOptionsListConstants; use Psr\Log\LoggerInterface; use Magento\Framework\Debug; +use Zend\Http\PhpEnvironment\Request as Environment; /** * Entry point for retrieving static resources like JS, CSS, images by requested public path @@ -74,6 +75,11 @@ class StaticResource implements \Magento\Framework\AppInterface */ private $logger; + /** + * @var Environment + */ + private $env; + /** * @param State $state * @param Response\FileInterface $response @@ -84,6 +90,8 @@ class StaticResource implements \Magento\Framework\AppInterface * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param ConfigLoaderInterface $configLoader * @param DeploymentConfig|null $deploymentConfig + * @param Environment|null $env + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( State $state, @@ -94,7 +102,8 @@ public function __construct( \Magento\Framework\Module\ModuleList $moduleList, \Magento\Framework\ObjectManagerInterface $objectManager, ConfigLoaderInterface $configLoader, - DeploymentConfig $deploymentConfig = null + DeploymentConfig $deploymentConfig = null, + ?Environment $env = null ) { $this->state = $state; $this->response = $response; @@ -105,6 +114,7 @@ public function __construct( $this->objectManager = $objectManager; $this->configLoader = $configLoader; $this->deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->get(DeploymentConfig::class); + $this->env = $env ?: ObjectManager::getInstance()->create(Environment::class); } /** @@ -153,7 +163,7 @@ public function catchException(Bootstrap $bootstrap, \Exception $exception) $exception->getTrace(), true, true, - false + boolval($this->env->getEnv('DEBUG_SHOW_ARGS', true)) ) ); $this->response->sendResponse(); diff --git a/lib/internal/Magento/Framework/Message/Manager.php b/lib/internal/Magento/Framework/Message/Manager.php index 1b39a777063ed..f59003970750b 100644 --- a/lib/internal/Magento/Framework/Message/Manager.php +++ b/lib/internal/Magento/Framework/Message/Manager.php @@ -9,6 +9,7 @@ use Psr\Log\LoggerInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Debug; +use Zend\Http\PhpEnvironment\Request as Environment; /** * Message manager model @@ -61,6 +62,11 @@ class Manager implements ManagerInterface */ private $exceptionMessageFactory; + /** + * @var Environment + */ + private $env; + /** * @param Session $session * @param Factory $messageFactory @@ -69,6 +75,7 @@ class Manager implements ManagerInterface * @param LoggerInterface $logger * @param string $defaultGroup * @param ExceptionMessageFactoryInterface|null $exceptionMessageFactory + * @param Environment|null $env */ public function __construct( Session $session, @@ -77,7 +84,8 @@ public function __construct( Event\ManagerInterface $eventManager, LoggerInterface $logger, $defaultGroup = self::DEFAULT_GROUP, - ExceptionMessageFactoryInterface $exceptionMessageFactory = null + ExceptionMessageFactoryInterface $exceptionMessageFactory = null, + ?Environment $env = null ) { $this->session = $session; $this->messageFactory = $messageFactory; @@ -87,6 +95,7 @@ public function __construct( $this->defaultGroup = $defaultGroup; $this->exceptionMessageFactory = $exceptionMessageFactory ?: ObjectManager::getInstance() ->get(ExceptionMessageLookupFactory::class); + $this->env = $env ?: ObjectManager::getInstance()->create(Environment::class); } /** @@ -253,7 +262,7 @@ public function addException(\Exception $exception, $alternativeText = null, $gr $exception->getTrace(), true, true, - false + boolval($this->env->getEnv('DEBUG_SHOW_ARGS', true)) ) ); From 205be7f7a2ebbbaeb3a6710c71fa7c0c478b56d9 Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 9 Jan 2019 12:35:28 +0200 Subject: [PATCH 075/275] MAGETWO-96505: Fixed incorrect stacktrace displaying --- lib/internal/Magento/Framework/App/Http.php | 16 +++------------- .../Magento/Framework/App/StaticResource.php | 14 ++------------ .../Magento/Framework/Message/Manager.php | 15 +++------------ 3 files changed, 8 insertions(+), 37 deletions(-) diff --git a/lib/internal/Magento/Framework/App/Http.php b/lib/internal/Magento/Framework/App/Http.php index 1b9db1d4069be..745f1639c7efd 100644 --- a/lib/internal/Magento/Framework/App/Http.php +++ b/lib/internal/Magento/Framework/App/Http.php @@ -14,7 +14,6 @@ use Magento\Framework\Controller\ResultInterface; use Magento\Framework\Event; use Magento\Framework\Filesystem; -use Zend\Http\PhpEnvironment\Request as Environment; /** * HTTP web application. Called from webroot index.php to serve web requests. @@ -73,11 +72,6 @@ class Http implements \Magento\Framework\AppInterface */ private $logger; - /** - * @var Environment - */ - private $env; - /** * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param Event\Manager $eventManager @@ -88,8 +82,6 @@ class Http implements \Magento\Framework\AppInterface * @param State $state * @param Filesystem $filesystem * @param \Magento\Framework\Registry $registry - * @param Environment|null $env - * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\ObjectManagerInterface $objectManager, @@ -100,8 +92,7 @@ public function __construct( ConfigLoaderInterface $configLoader, State $state, Filesystem $filesystem, - \Magento\Framework\Registry $registry, - ?Environment $env = null + \Magento\Framework\Registry $registry ) { $this->_objectManager = $objectManager; $this->_eventManager = $eventManager; @@ -112,7 +103,6 @@ public function __construct( $this->_state = $state; $this->_filesystem = $filesystem; $this->registry = $registry; - $this->env = $env ?: ObjectManager::getInstance()->create(Environment::class); } /** @@ -230,7 +220,7 @@ private function buildContentFromException(\Exception $exception) $exception->getTrace(), true, true, - boolval($this->env->getEnv('DEBUG_SHOW_ARGS', true)) + boolval(getenv('DEBUG_SHOW_ARGS')) ) ); } @@ -335,7 +325,7 @@ private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception $exception->getTrace(), true, true, - (bool)DEBUG_BACKTRACE_IGNORE_ARGS + boolval(getenv('DEBUG_SHOW_ARGS')) ) ]; $params = $bootstrap->getParams(); diff --git a/lib/internal/Magento/Framework/App/StaticResource.php b/lib/internal/Magento/Framework/App/StaticResource.php index bbb63d3dbe580..2d9dbe2165a61 100644 --- a/lib/internal/Magento/Framework/App/StaticResource.php +++ b/lib/internal/Magento/Framework/App/StaticResource.php @@ -11,7 +11,6 @@ use Magento\Framework\Config\ConfigOptionsListConstants; use Psr\Log\LoggerInterface; use Magento\Framework\Debug; -use Zend\Http\PhpEnvironment\Request as Environment; /** * Entry point for retrieving static resources like JS, CSS, images by requested public path @@ -75,11 +74,6 @@ class StaticResource implements \Magento\Framework\AppInterface */ private $logger; - /** - * @var Environment - */ - private $env; - /** * @param State $state * @param Response\FileInterface $response @@ -90,8 +84,6 @@ class StaticResource implements \Magento\Framework\AppInterface * @param \Magento\Framework\ObjectManagerInterface $objectManager * @param ConfigLoaderInterface $configLoader * @param DeploymentConfig|null $deploymentConfig - * @param Environment|null $env - * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( State $state, @@ -102,8 +94,7 @@ public function __construct( \Magento\Framework\Module\ModuleList $moduleList, \Magento\Framework\ObjectManagerInterface $objectManager, ConfigLoaderInterface $configLoader, - DeploymentConfig $deploymentConfig = null, - ?Environment $env = null + DeploymentConfig $deploymentConfig = null ) { $this->state = $state; $this->response = $response; @@ -114,7 +105,6 @@ public function __construct( $this->objectManager = $objectManager; $this->configLoader = $configLoader; $this->deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->get(DeploymentConfig::class); - $this->env = $env ?: ObjectManager::getInstance()->create(Environment::class); } /** @@ -163,7 +153,7 @@ public function catchException(Bootstrap $bootstrap, \Exception $exception) $exception->getTrace(), true, true, - boolval($this->env->getEnv('DEBUG_SHOW_ARGS', true)) + boolval(getenv('DEBUG_SHOW_ARGS')) ) ); $this->response->sendResponse(); diff --git a/lib/internal/Magento/Framework/Message/Manager.php b/lib/internal/Magento/Framework/Message/Manager.php index f59003970750b..bae6be8e99bf3 100644 --- a/lib/internal/Magento/Framework/Message/Manager.php +++ b/lib/internal/Magento/Framework/Message/Manager.php @@ -9,7 +9,6 @@ use Psr\Log\LoggerInterface; use Magento\Framework\App\ObjectManager; use Magento\Framework\Debug; -use Zend\Http\PhpEnvironment\Request as Environment; /** * Message manager model @@ -62,11 +61,6 @@ class Manager implements ManagerInterface */ private $exceptionMessageFactory; - /** - * @var Environment - */ - private $env; - /** * @param Session $session * @param Factory $messageFactory @@ -75,7 +69,6 @@ class Manager implements ManagerInterface * @param LoggerInterface $logger * @param string $defaultGroup * @param ExceptionMessageFactoryInterface|null $exceptionMessageFactory - * @param Environment|null $env */ public function __construct( Session $session, @@ -84,8 +77,7 @@ public function __construct( Event\ManagerInterface $eventManager, LoggerInterface $logger, $defaultGroup = self::DEFAULT_GROUP, - ExceptionMessageFactoryInterface $exceptionMessageFactory = null, - ?Environment $env = null + ExceptionMessageFactoryInterface $exceptionMessageFactory = null ) { $this->session = $session; $this->messageFactory = $messageFactory; @@ -95,7 +87,6 @@ public function __construct( $this->defaultGroup = $defaultGroup; $this->exceptionMessageFactory = $exceptionMessageFactory ?: ObjectManager::getInstance() ->get(ExceptionMessageLookupFactory::class); - $this->env = $env ?: ObjectManager::getInstance()->create(Environment::class); } /** @@ -262,7 +253,7 @@ public function addException(\Exception $exception, $alternativeText = null, $gr $exception->getTrace(), true, true, - boolval($this->env->getEnv('DEBUG_SHOW_ARGS', true)) + boolval(getenv('DEBUG_SHOW_ARGS')) ) ); @@ -305,7 +296,7 @@ public function addExceptionMessage(\Exception $exception, $alternativeText = nu $exception->getTrace(), true, true, - false + boolval(getenv('DEBUG_SHOW_ARGS')) ) ); From c351a34d9a58d1fba366439dc15d3a4f8914ff86 Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 9 Jan 2019 13:35:41 +0200 Subject: [PATCH 076/275] MAGETWO-96505: Fixed incorrect stacktrace displaying --- .htaccess | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.htaccess b/.htaccess index d22b5a1395cae..0d94ab76bab52 100644 --- a/.htaccess +++ b/.htaccess @@ -29,6 +29,8 @@ ############################################ ## default index file +## Specifies option, to use methods arguments in backtrace or not + SetEnv DEBUG_SHOW_ARGS 0 DirectoryIndex index.php From ff46fc02ca1454a1f965a6ef3f5abf38b7aea997 Mon Sep 17 00:00:00 2001 From: roman Date: Wed, 9 Jan 2019 14:34:12 +0200 Subject: [PATCH 077/275] MAGETWO-96757: Fixed incorrect displaying of the sales rule conditions --- .../Adminhtml/Promo/Quote/NewConditionHtml.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php index 715f5a3630a6a..50545fd864866 100644 --- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php +++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/NewConditionHtml.php @@ -1,12 +1,16 @@ getResponse()->setBody($html); } -} \ No newline at end of file +} From d23af6e295b9aea9b643386f39ec1f07b5533371 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 10 Jan 2019 13:00:16 +0200 Subject: [PATCH 078/275] MAGETWO-96505: Fixed incorrect stacktrace displaying --- .htaccess | 2 +- lib/internal/Magento/Framework/App/Http.php | 4 ++-- lib/internal/Magento/Framework/App/StaticResource.php | 2 +- lib/internal/Magento/Framework/Message/Manager.php | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.htaccess b/.htaccess index 0d94ab76bab52..1fbef84693823 100644 --- a/.htaccess +++ b/.htaccess @@ -30,7 +30,7 @@ ############################################ ## default index file ## Specifies option, to use methods arguments in backtrace or not - SetEnv DEBUG_SHOW_ARGS 0 + SetEnv MAGE_DEBUG_SHOW_ARGS 1 DirectoryIndex index.php diff --git a/lib/internal/Magento/Framework/App/Http.php b/lib/internal/Magento/Framework/App/Http.php index 745f1639c7efd..23024a44c2def 100644 --- a/lib/internal/Magento/Framework/App/Http.php +++ b/lib/internal/Magento/Framework/App/Http.php @@ -220,7 +220,7 @@ private function buildContentFromException(\Exception $exception) $exception->getTrace(), true, true, - boolval(getenv('DEBUG_SHOW_ARGS')) + (bool)getenv('MAGE_DEBUG_SHOW_ARGS') ) ); } @@ -325,7 +325,7 @@ private function handleGenericReport(Bootstrap $bootstrap, \Exception $exception $exception->getTrace(), true, true, - boolval(getenv('DEBUG_SHOW_ARGS')) + (bool)getenv('MAGE_DEBUG_SHOW_ARGS') ) ]; $params = $bootstrap->getParams(); diff --git a/lib/internal/Magento/Framework/App/StaticResource.php b/lib/internal/Magento/Framework/App/StaticResource.php index 2d9dbe2165a61..86b2b15d3c446 100644 --- a/lib/internal/Magento/Framework/App/StaticResource.php +++ b/lib/internal/Magento/Framework/App/StaticResource.php @@ -153,7 +153,7 @@ public function catchException(Bootstrap $bootstrap, \Exception $exception) $exception->getTrace(), true, true, - boolval(getenv('DEBUG_SHOW_ARGS')) + (bool)getenv('MAGE_DEBUG_SHOW_ARGS') ) ); $this->response->sendResponse(); diff --git a/lib/internal/Magento/Framework/Message/Manager.php b/lib/internal/Magento/Framework/Message/Manager.php index bae6be8e99bf3..c70798bf0281f 100644 --- a/lib/internal/Magento/Framework/Message/Manager.php +++ b/lib/internal/Magento/Framework/Message/Manager.php @@ -253,7 +253,7 @@ public function addException(\Exception $exception, $alternativeText = null, $gr $exception->getTrace(), true, true, - boolval(getenv('DEBUG_SHOW_ARGS')) + (bool)getenv('DEBUG_SHOW_ARGS') ) ); @@ -296,7 +296,7 @@ public function addExceptionMessage(\Exception $exception, $alternativeText = nu $exception->getTrace(), true, true, - boolval(getenv('DEBUG_SHOW_ARGS')) + (bool)getenv('MAGE_DEBUG_SHOW_ARGS') ) ); From c4e4487bade6c25d6625ec38e580ecabc88439c8 Mon Sep 17 00:00:00 2001 From: roman Date: Thu, 10 Jan 2019 13:28:29 +0200 Subject: [PATCH 079/275] MAGETWO-96505: Fixed incorrect stacktrace displaying --- lib/internal/Magento/Framework/Message/Manager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Message/Manager.php b/lib/internal/Magento/Framework/Message/Manager.php index c70798bf0281f..37ca1e7e2b725 100644 --- a/lib/internal/Magento/Framework/Message/Manager.php +++ b/lib/internal/Magento/Framework/Message/Manager.php @@ -253,7 +253,7 @@ public function addException(\Exception $exception, $alternativeText = null, $gr $exception->getTrace(), true, true, - (bool)getenv('DEBUG_SHOW_ARGS') + (bool)getenv('MAGE_DEBUG_SHOW_ARGS') ) ); From a0566abffe872cca0a7e06eb2b4d08093f15fbda Mon Sep 17 00:00:00 2001 From: Fabian Schmengler Date: Fri, 11 Jan 2019 11:18:28 +0100 Subject: [PATCH 080/275] Deny access to XML and PHTML files in pub/errors For apache via .htaccess and in nginx sample configuration --- nginx.conf.sample | 7 ++++++- pub/errors/.htaccess | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/nginx.conf.sample b/nginx.conf.sample index 90604808f6ec0..80cc88431eff8 100644 --- a/nginx.conf.sample +++ b/nginx.conf.sample @@ -159,6 +159,11 @@ location /media/downloadable/ { location /media/import/ { deny all; } +location /errors/ { + location ~* \.xml$ { + deny all; + } +} # PHP entry point for main application location ~ ^/(index|get|static|errors/report|errors/404|errors/503|health_check)\.php$ { @@ -198,6 +203,6 @@ gzip_types gzip_vary on; # Banned locations (only reached if the earlier PHP entry point regexes don't match) -location ~* (\.php$|\.htaccess$|\.git) { +location ~* (\.php$|\.phtml$|\.htaccess$|\.git) { deny all; } diff --git a/pub/errors/.htaccess b/pub/errors/.htaccess index 3692dd439e2ff..a7b9cbda05893 100644 --- a/pub/errors/.htaccess +++ b/pub/errors/.htaccess @@ -1,4 +1,7 @@ Options None + + Deny from all + RewriteEngine Off From d8e222f81c43464e0f8c91d669d83026f663fe02 Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Wed, 16 Jan 2019 17:19:14 -0600 Subject: [PATCH 081/275] MC-10866: Cart's customer and address mismatch --- .../Quote/Address/BillingAddressPersister.php | 2 +- .../Quote/Model/QuoteAddressValidator.php | 66 +++++++-- .../Quote/Model/ShippingAddressManagement.php | 2 +- ...GuestShippingInformationManagementTest.php | 125 ++++++++++++++++++ .../Api/ShippingInformationManagementTest.php | 104 +++++++++++++++ .../_files/customer_with_addresses.php | 75 +++++++++++ .../customer_with_addresses_rollback.php | 34 +++++ 7 files changed, 392 insertions(+), 16 deletions(-) create mode 100644 dev/tests/integration/testsuite/Magento/Checkout/Api/GuestShippingInformationManagementTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Checkout/Api/ShippingInformationManagementTest.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_addresses.php create mode 100644 dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_addresses_rollback.php diff --git a/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php b/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php index c5b8dc1c4b124..c0f90cb55e1f1 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php +++ b/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php @@ -47,7 +47,7 @@ public function __construct( public function save(CartInterface $quote, AddressInterface $address, $useForShipping = false) { /** @var \Magento\Quote\Model\Quote $quote */ - $this->addressValidator->validate($address); + $this->addressValidator->validateForCart($quote, $address); $customerAddressId = $address->getCustomerAddressId(); $shippingAddress = null; $addressData = []; diff --git a/app/code/Magento/Quote/Model/QuoteAddressValidator.php b/app/code/Magento/Quote/Model/QuoteAddressValidator.php index 9a86829bfc4ce..8bfc97e9920b9 100644 --- a/app/code/Magento/Quote/Model/QuoteAddressValidator.php +++ b/app/code/Magento/Quote/Model/QuoteAddressValidator.php @@ -6,6 +6,8 @@ namespace Magento\Quote\Model; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Quote\Api\Data\AddressInterface; +use Magento\Quote\Api\Data\CartInterface; /** * Quote shipping/billing address validator service. @@ -50,44 +52,80 @@ public function __construct( } /** - * Validates the fields in a specified address data object. + * Validate address. * - * @param \Magento\Quote\Api\Data\AddressInterface $addressData The address data object. - * @return bool + * @param AddressInterface $address + * @param int|null $customerId Cart belongs to + * @return void * @throws \Magento\Framework\Exception\InputException The specified address belongs to another customer. * @throws \Magento\Framework\Exception\NoSuchEntityException The specified customer ID or address ID is not valid. */ - public function validate(\Magento\Quote\Api\Data\AddressInterface $addressData) + private function doValidate(AddressInterface $address, ?int $customerId): void { //validate customer id - if ($addressData->getCustomerId()) { - $customer = $this->customerRepository->getById($addressData->getCustomerId()); + if ($customerId) { + $customer = $this->customerRepository->getById($customerId); if (!$customer->getId()) { throw new \Magento\Framework\Exception\NoSuchEntityException( - __('Invalid customer id %1', $addressData->getCustomerId()) + __('Invalid customer id %1', $customerId) ); } } - if ($addressData->getCustomerAddressId()) { + if ($address->getCustomerAddressId()) { + //Existing address cannot belong to a guest + if (!$customerId) { + throw new \Magento\Framework\Exception\NoSuchEntityException( + __('Invalid customer address id %1', $address->getCustomerAddressId()) + ); + } + //Validating address ID try { - $this->addressRepository->getById($addressData->getCustomerAddressId()); + $this->addressRepository->getById($address->getCustomerAddressId()); } catch (NoSuchEntityException $e) { throw new \Magento\Framework\Exception\NoSuchEntityException( - __('Invalid address id %1', $addressData->getId()) + __('Invalid address id %1', $address->getId()) ); } - + //Finding available customer's addresses $applicableAddressIds = array_map(function ($address) { /** @var \Magento\Customer\Api\Data\AddressInterface $address */ return $address->getId(); - }, $this->customerRepository->getById($addressData->getCustomerId())->getAddresses()); - if (!in_array($addressData->getCustomerAddressId(), $applicableAddressIds)) { + }, $this->customerRepository->getById($customerId)->getAddresses()); + if (!in_array($address->getCustomerAddressId(), $applicableAddressIds)) { throw new \Magento\Framework\Exception\NoSuchEntityException( - __('Invalid customer address id %1', $addressData->getCustomerAddressId()) + __('Invalid customer address id %1', $address->getCustomerAddressId()) ); } } + } + + /** + * Validates the fields in a specified address data object. + * + * @param \Magento\Quote\Api\Data\AddressInterface $addressData The address data object. + * @return bool + * @throws \Magento\Framework\Exception\InputException The specified address belongs to another customer. + * @throws \Magento\Framework\Exception\NoSuchEntityException The specified customer ID or address ID is not valid. + */ + public function validate(AddressInterface $addressData) + { + $this->doValidate($addressData, $addressData->getCustomerId()); + return true; } + + /** + * Validate address to be used for cart. + * + * @param CartInterface $cart + * @param AddressInterface $address + * @return void + * @throws \Magento\Framework\Exception\InputException The specified address belongs to another customer. + * @throws \Magento\Framework\Exception\NoSuchEntityException The specified customer ID or address ID is not valid. + */ + public function validateForCart(CartInterface $cart, AddressInterface $address): void + { + $this->doValidate($address, $cart->getCustomerIsGuest() ? null : $cart->getCustomer()->getId()); + } } diff --git a/app/code/Magento/Quote/Model/ShippingAddressManagement.php b/app/code/Magento/Quote/Model/ShippingAddressManagement.php index d8e70c68ba33f..b006dcf7814d5 100644 --- a/app/code/Magento/Quote/Model/ShippingAddressManagement.php +++ b/app/code/Magento/Quote/Model/ShippingAddressManagement.php @@ -95,7 +95,7 @@ public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $addres $saveInAddressBook = $address->getSaveInAddressBook() ? 1 : 0; $sameAsBilling = $address->getSameAsBilling() ? 1 : 0; $customerAddressId = $address->getCustomerAddressId(); - $this->addressValidator->validate($address); + $this->addressValidator->validateForCart($quote, $address); $quote->setShippingAddress($address); $address = $quote->getShippingAddress(); diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Api/GuestShippingInformationManagementTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Api/GuestShippingInformationManagementTest.php new file mode 100644 index 0000000000000..50b1256c0f124 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/Api/GuestShippingInformationManagementTest.php @@ -0,0 +1,125 @@ +management = $objectManager->get(GuestShippingInformationManagementInterface::class); + $this->cartRepo = $objectManager->get(CartRepositoryInterface::class); + $this->customerRepo = $objectManager->get(CustomerRepositoryInterface::class); + $this->shippingFactory = $objectManager->get(ShippingInformationInterfaceFactory::class); + $this->searchCriteria = $objectManager->get(SearchCriteriaBuilder::class); + $this->maskFactory = $objectManager->get(QuoteIdMaskFactory::class); + } + + /** + * Test using another address for quote. + * + * @param bool $swapShipping Whether to swap shipping or billing addresses. + * @return void + * + * @magentoDataFixture Magento/Sales/_files/quote.php + * @magentoDataFixture Magento/Customer/_files/customer_with_addresses.php + * @dataProvider getAddressesVariation + * @expectedException \Magento\Framework\Exception\InputException + * @expectedExceptionMessage The shipping information was unable to be saved. Verify the input data and try again. + */ + public function testDifferentAddresses(bool $swapShipping) + { + $carts = $this->cartRepo->getList( + $this->searchCriteria->addFilter('reserved_order_id', 'test01')->create() + )->getItems(); + $cart = array_pop($carts); + $otherCustomer = $this->customerRepo->get('customer_with_addresses@test.com'); + $otherAddresses = $otherCustomer->getAddresses(); + $otherAddress = array_pop($otherAddresses); + + //Setting invalid IDs. + /** @var ShippingAssignmentInterface $shippingAssignment */ + $shippingAssignment = $cart->getExtensionAttributes()->getShippingAssignments()[0]; + $shippingAddress = $shippingAssignment->getShipping()->getAddress(); + $billingAddress = $cart->getBillingAddress(); + if ($swapShipping) { + $address = $shippingAddress; + } else { + $address = $billingAddress; + } + $address->setCustomerAddressId($otherAddress->getId()); + $address->setCustomerId($otherCustomer->getId()); + $address->setId(null); + /** @var ShippingInformationInterface $shippingInformation */ + $shippingInformation = $this->shippingFactory->create(); + $shippingInformation->setBillingAddress($billingAddress); + $shippingInformation->setShippingAddress($shippingAddress); + $shippingInformation->setShippingMethodCode('flatrate'); + /** @var QuoteIdMask $idMask */ + $idMask = $this->maskFactory->create(); + $idMask->load($cart->getId(), 'quote_id'); + $this->management->saveAddressInformation($idMask->getMaskedId(), $shippingInformation); + } + + /** + * Different variations for addresses test. + * + * @return array + */ + public function getAddressesVariation(): array + { + return [ + 'Shipping address swap' => [true], + 'Billing address swap' => [false] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Api/ShippingInformationManagementTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Api/ShippingInformationManagementTest.php new file mode 100644 index 0000000000000..7440fb7fd3d98 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Checkout/Api/ShippingInformationManagementTest.php @@ -0,0 +1,104 @@ +management = $objectManager->get(ShippingInformationManagementInterface::class); + $this->cartRepo = $objectManager->get(CartRepositoryInterface::class); + $this->customerRepo = $objectManager->get(CustomerRepositoryInterface::class); + $this->shippingFactory = $objectManager->get(ShippingInformationInterfaceFactory::class); + } + + /** + * Test using another address for quote. + * + * @param bool $swapShipping Whether to swap shipping or billing addresses. + * @return void + * + * @magentoDataFixture Magento/Sales/_files/quote_with_customer.php + * @magentoDataFixture Magento/Customer/_files/customer_with_addresses.php + * @dataProvider getAddressesVariation + * @expectedException \Magento\Framework\Exception\InputException + * @expectedExceptionMessage The shipping information was unable to be saved. Verify the input data and try again. + */ + public function testDifferentAddresses(bool $swapShipping) + { + $cart = $this->cartRepo->getForCustomer(1); + $otherCustomer = $this->customerRepo->get('customer_with_addresses@test.com'); + $otherAddresses = $otherCustomer->getAddresses(); + $otherAddress = array_pop($otherAddresses); + + //Setting invalid IDs. + /** @var ShippingAssignmentInterface $shippingAssignment */ + $shippingAssignment = $cart->getExtensionAttributes()->getShippingAssignments()[0]; + $shippingAddress = $shippingAssignment->getShipping()->getAddress(); + $billingAddress = $cart->getBillingAddress(); + if ($swapShipping) { + $address = $shippingAddress; + } else { + $address = $billingAddress; + } + $address->setCustomerAddressId($otherAddress->getId()); + $address->setCustomerId($otherCustomer->getId()); + $address->setId(null); + /** @var ShippingInformationInterface $shippingInformation */ + $shippingInformation = $this->shippingFactory->create(); + $shippingInformation->setBillingAddress($billingAddress); + $shippingInformation->setShippingAddress($shippingAddress); + $shippingInformation->setShippingMethodCode('flatrate'); + $this->management->saveAddressInformation($cart->getId(), $shippingInformation); + } + + /** + * Different variations for addresses test. + * + * @return array + */ + public function getAddressesVariation(): array + { + return [ + 'Shipping address swap' => [true], + 'Billing address swap' => [false] + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_addresses.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_addresses.php new file mode 100644 index 0000000000000..60b570b9d13d1 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_addresses.php @@ -0,0 +1,75 @@ +create(CustomerRepositoryInterface::class); +/** @var Customer $customer */ +$customer = $objectManager->create(Customer::class); +/** @var CustomerRegistry $customerRegistry */ +$customerRegistry = $objectManager->get(CustomerRegistry::class); +$customer->setWebsiteId(1) + ->setEmail('customer_with_addresses@test.com') + ->setPassword('password') + ->setGroupId(1) + ->setStoreId(1) + ->setIsActive(1) + ->setPrefix('Mr.') + ->setFirstname('John') + ->setMiddlename('A') + ->setLastname('Smith') + ->setSuffix('Esq.') + ->setDefaultBilling(1) + ->setDefaultShipping(1) + ->setTaxvat('12') + ->setGender(0); + +$customer->isObjectNew(true); +$customer->save(); +$customerRegistry->remove($customer->getId()); + +//Creating address +/** @var Address $customerAddress */ +$customerAddress = $objectManager->create(Address::class); +$customerAddress->isObjectNew(true); +$customerAddress->setData( + [ + 'attribute_set_id' => 2, + 'telephone' => 3468676, + 'postcode' => 75477, + 'country_id' => 'US', + 'city' => 'CityM', + 'company' => 'CompanyName', + 'street' => 'CustomerAddress1', + 'lastname' => 'Smith', + 'firstname' => 'John', + 'parent_id' => $customer->getId(), + 'region_id' => 1, + ] +); +$customerAddress->save(); +/** @var AddressRepositoryInterface $addressRepository */ +$addressRepository = $objectManager->get(AddressRepositoryInterface::class); +$customerAddress = $addressRepository->getById($customerAddress->getId()); +$customerAddress->setCustomerId($customer->getId()); +$customerAddress->isDefaultBilling(true); +$customerAddress->setIsDefaultShipping(true); +$customerAddress = $addressRepository->save($customerAddress); +$customerRegistry->remove($customerAddress->getCustomerId()); +/** @var AddressRegistry $addressRegistry */ +$addressRegistry = $objectManager->get(AddressRegistry::class); +$addressRegistry->remove($customerAddress->getId()); diff --git a/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_addresses_rollback.php b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_addresses_rollback.php new file mode 100644 index 0000000000000..e0c62bffc70d2 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/_files/customer_with_addresses_rollback.php @@ -0,0 +1,34 @@ +get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); +/** @var CustomerRepositoryInterface $customerRepo */ +$customerRepo = $objectManager->get(CustomerRepositoryInterface::class); +try { + $customer = $customerRepo->get('customer_with_addresses@test.com'); + /** @var AddressRepositoryInterface $addressRepo */ + $addressRepo = $objectManager->get(AddressRepositoryInterface::class); + foreach ($customer->getAddresses() as $address) { + $addressRepo->delete($address); + } + $customerRepo->delete($customer); +} catch (NoSuchEntityException $exception) { + //Already deleted +} +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); From 6f81c9d5c63b136ce293e2ee84f262201c5b488d Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Thu, 17 Jan 2019 11:08:54 -0600 Subject: [PATCH 082/275] MC-10866: Cart's customer and address mismatch --- .../Quote/Address/BillingAddressPersister.php | 5 + .../Quote/Model/QuoteAddressValidator.php | 1 - .../Quote/Model/ShippingAddressManagement.php | 4 +- .../Unit/Model/QuoteAddressValidatorTest.php | 128 -------- .../Model/ShippingAddressManagementTest.php | 282 ------------------ 5 files changed, 7 insertions(+), 413 deletions(-) delete mode 100644 app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php delete mode 100644 app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php diff --git a/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php b/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php index c0f90cb55e1f1..6fdb70350ed72 100644 --- a/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php +++ b/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php @@ -12,6 +12,9 @@ use Magento\Quote\Model\QuoteAddressValidator; use Magento\Customer\Api\AddressRepositoryInterface; +/** + * Saves billing address for quotes. + */ class BillingAddressPersister { /** @@ -37,6 +40,8 @@ public function __construct( } /** + * Save address for billing. + * * @param CartInterface $quote * @param AddressInterface $address * @param bool $useForShipping diff --git a/app/code/Magento/Quote/Model/QuoteAddressValidator.php b/app/code/Magento/Quote/Model/QuoteAddressValidator.php index 8bfc97e9920b9..50807f71a29d0 100644 --- a/app/code/Magento/Quote/Model/QuoteAddressValidator.php +++ b/app/code/Magento/Quote/Model/QuoteAddressValidator.php @@ -11,7 +11,6 @@ /** * Quote shipping/billing address validator service. - * */ class QuoteAddressValidator { diff --git a/app/code/Magento/Quote/Model/ShippingAddressManagement.php b/app/code/Magento/Quote/Model/ShippingAddressManagement.php index b006dcf7814d5..b9edcc13d0077 100644 --- a/app/code/Magento/Quote/Model/ShippingAddressManagement.php +++ b/app/code/Magento/Quote/Model/ShippingAddressManagement.php @@ -79,7 +79,7 @@ public function __construct( } /** - * {@inheritDoc} + * @inheritDoc * @SuppressWarnings(PHPMD.NPathComplexity) */ public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $address) @@ -123,7 +123,7 @@ public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $addres } /** - * {@inheritDoc} + * @inheritDoc */ public function get($cartId) { diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php deleted file mode 100644 index 08f5f6a808561..0000000000000 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php +++ /dev/null @@ -1,128 +0,0 @@ -objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - - $this->addressRepositoryMock = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class); - $this->quoteAddressMock = $this->createPartialMock( - \Magento\Quote\Model\Quote\Address::class, - ['getCustomerId', 'load', 'getId', '__wakeup'] - ); - $this->customerRepositoryMock = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class); - $this->customerSessionMock = $this->createMock(\Magento\Customer\Model\Session::class); - $this->model = $this->objectManager->getObject( - \Magento\Quote\Model\QuoteAddressValidator::class, - [ - 'addressRepository' => $this->addressRepositoryMock, - 'customerRepository' => $this->customerRepositoryMock, - 'customerSession' => $this->customerSessionMock - ] - ); - } - - /** - * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedExceptionMessage Invalid customer id 100 - */ - public function testValidateInvalidCustomer() - { - $customerId = 100; - $address = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); - $customerMock = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); - - $address->expects($this->atLeastOnce())->method('getCustomerId')->willReturn($customerId); - $this->customerRepositoryMock->expects($this->once())->method('getById')->with($customerId) - ->willReturn($customerMock); - $this->model->validate($address); - } - - /** - * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedExceptionMessage Invalid address id 101 - */ - public function testValidateInvalidAddress() - { - $address = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); - $this->customerRepositoryMock->expects($this->never())->method('getById'); - $address->expects($this->atLeastOnce())->method('getCustomerAddressId')->willReturn(101); - $address->expects($this->once())->method('getId')->willReturn(101); - - $this->addressRepositoryMock->expects($this->once())->method('getById') - ->willThrowException(new \Magento\Framework\Exception\NoSuchEntityException()); - - $this->model->validate($address); - } - - /** - * Neither customer id used nor address id exists - */ - public function testValidateNewAddress() - { - $this->customerRepositoryMock->expects($this->never())->method('getById'); - $this->addressRepositoryMock->expects($this->never())->method('getById'); - $address = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); - $this->assertTrue($this->model->validate($address)); - } - - public function testValidateWithValidAddress() - { - $addressCustomer = 100; - $customerAddressId = 42; - - $address = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); - $address->expects($this->atLeastOnce())->method('getCustomerId')->willReturn($addressCustomer); - $address->expects($this->atLeastOnce())->method('getCustomerAddressId')->willReturn($customerAddressId); - $customerMock = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class); - $customerAddress = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class); - - $this->customerRepositoryMock->expects($this->exactly(2))->method('getById')->willReturn($customerMock); - $customerMock->expects($this->once())->method('getId')->willReturn($addressCustomer); - - $this->addressRepositoryMock->expects($this->once())->method('getById')->willReturn($this->quoteAddressMock); - $this->quoteAddressMock->expects($this->any())->method('getCustomerId')->willReturn($addressCustomer); - - $customerMock->expects($this->once())->method('getAddresses')->willReturn([$customerAddress]); - $customerAddress->expects($this->once())->method('getId')->willReturn(42); - - $this->assertTrue($this->model->validate($address)); - } -} diff --git a/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php deleted file mode 100644 index 89fea2bec73a8..0000000000000 --- a/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php +++ /dev/null @@ -1,282 +0,0 @@ -objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); - $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class); - $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); - - $this->quoteAddressMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Address::class, [ - 'setSameAsBilling', - 'setCollectShippingRates', - '__wakeup', - 'collectTotals', - 'save', - 'getId', - 'getCustomerAddressId', - 'getSaveInAddressBook', - 'getSameAsBilling', - 'importCustomerAddressData', - 'setSaveInAddressBook', - ]); - $this->validatorMock = $this->createMock(\Magento\Quote\Model\QuoteAddressValidator::class); - $this->totalsCollectorMock = $this->createMock(\Magento\Quote\Model\Quote\TotalsCollector::class); - $this->addressRepository = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class); - - $this->amountErrorMessageMock = $this->createPartialMock( - \Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage::class, - ['getMessage'] - ); - - $this->service = $this->objectManager->getObject( - \Magento\Quote\Model\ShippingAddressManagement::class, - [ - 'quoteRepository' => $this->quoteRepositoryMock, - 'addressValidator' => $this->validatorMock, - 'logger' => $this->createMock(\Psr\Log\LoggerInterface::class), - 'scopeConfig' => $this->scopeConfigMock, - 'totalsCollector' => $this->totalsCollectorMock, - 'addressRepository' => $this->addressRepository - ] - ); - } - - /** - * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedExceptionMessage error345 - */ - public function testSetAddressValidationFailed() - { - $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); - $this->quoteRepositoryMock->expects($this->once()) - ->method('getActive') - ->with('cart654') - ->will($this->returnValue($quoteMock)); - - $this->validatorMock->expects($this->once())->method('validate') - ->will($this->throwException(new \Magento\Framework\Exception\NoSuchEntityException(__('error345')))); - - $this->service->assign('cart654', $this->quoteAddressMock); - } - - public function testSetAddress() - { - $addressId = 1; - $customerAddressId = 150; - - $quoteMock = $this->createPartialMock( - \Magento\Quote\Model\Quote::class, - ['getIsMultiShipping', 'isVirtual', 'validateMinimumAmount', 'setShippingAddress', 'getShippingAddress'] - ); - $this->quoteRepositoryMock->expects($this->once()) - ->method('getActive') - ->with('cart867') - ->willReturn($quoteMock); - $quoteMock->expects($this->once())->method('isVirtual')->will($this->returnValue(false)); - $quoteMock->expects($this->once()) - ->method('setShippingAddress') - ->with($this->quoteAddressMock) - ->willReturnSelf(); - - $this->quoteAddressMock->expects($this->once())->method('getSaveInAddressBook')->willReturn(1); - $this->quoteAddressMock->expects($this->once())->method('getSameAsBilling')->willReturn(1); - $this->quoteAddressMock->expects($this->once())->method('getCustomerAddressId')->willReturn($customerAddressId); - - $customerAddressMock = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class); - - $this->addressRepository->expects($this->once()) - ->method('getById') - ->with($customerAddressId) - ->willReturn($customerAddressMock); - - $this->validatorMock->expects($this->once())->method('validate') - ->with($this->quoteAddressMock) - ->willReturn(true); - - $quoteMock->expects($this->exactly(3))->method('getShippingAddress')->willReturn($this->quoteAddressMock); - $this->quoteAddressMock->expects($this->once()) - ->method('importCustomerAddressData') - ->with($customerAddressMock) - ->willReturnSelf(); - - $this->quoteAddressMock->expects($this->once())->method('setSameAsBilling')->with(1)->willReturnSelf(); - $this->quoteAddressMock->expects($this->once())->method('setSaveInAddressBook')->with(1)->willReturnSelf(); - $this->quoteAddressMock->expects($this->once()) - ->method('setCollectShippingRates') - ->with(true) - ->willReturnSelf(); - - $this->quoteAddressMock->expects($this->once())->method('save')->willReturnSelf(); - $this->quoteAddressMock->expects($this->once())->method('getId')->will($this->returnValue($addressId)); - - $this->assertEquals($addressId, $this->service->assign('cart867', $this->quoteAddressMock)); - } - - /** - * @expectedException \Magento\Framework\Exception\NoSuchEntityException - * @expectedExceptionMessage The Cart includes virtual product(s) only, so a shipping address is not used. - */ - public function testSetAddressForVirtualProduct() - { - $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); - $this->quoteRepositoryMock->expects($this->once()) - ->method('getActive') - ->with('cart867') - ->will($this->returnValue($quoteMock)); - $quoteMock->expects($this->once())->method('isVirtual')->will($this->returnValue(true)); - $quoteMock->expects($this->never())->method('setShippingAddress'); - - $this->quoteAddressMock->expects($this->never())->method('getCustomerAddressId'); - $this->quoteAddressMock->expects($this->never())->method('setSaveInAddressBook'); - - $quoteMock->expects($this->never())->method('save'); - - $this->service->assign('cart867', $this->quoteAddressMock); - } - - /** - * @expectedException \Magento\Framework\Exception\InputException - * @expectedExceptionMessage The address failed to save. Verify the address and try again. - */ - public function testSetAddressWithInabilityToSaveQuote() - { - $this->quoteAddressMock->expects($this->once())->method('save')->willThrowException( - new \Exception('The address failed to save. Verify the address and try again.') - ); - - $customerAddressId = 150; - - $quoteMock = $this->createPartialMock( - \Magento\Quote\Model\Quote::class, - ['getIsMultiShipping', 'isVirtual', 'validateMinimumAmount', 'setShippingAddress', 'getShippingAddress'] - ); - $this->quoteRepositoryMock->expects($this->once()) - ->method('getActive') - ->with('cart867') - ->willReturn($quoteMock); - $quoteMock->expects($this->once())->method('isVirtual')->will($this->returnValue(false)); - $quoteMock->expects($this->once()) - ->method('setShippingAddress') - ->with($this->quoteAddressMock) - ->willReturnSelf(); - - $customerAddressMock = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class); - - $this->addressRepository->expects($this->once()) - ->method('getById') - ->with($customerAddressId) - ->willReturn($customerAddressMock); - - $this->validatorMock->expects($this->once())->method('validate') - ->with($this->quoteAddressMock) - ->willReturn(true); - - $this->quoteAddressMock->expects($this->once())->method('getSaveInAddressBook')->willReturn(1); - $this->quoteAddressMock->expects($this->once())->method('getSameAsBilling')->willReturn(1); - $this->quoteAddressMock->expects($this->once())->method('getCustomerAddressId')->willReturn($customerAddressId); - - $quoteMock->expects($this->exactly(2))->method('getShippingAddress')->willReturn($this->quoteAddressMock); - $this->quoteAddressMock->expects($this->once()) - ->method('importCustomerAddressData') - ->with($customerAddressMock) - ->willReturnSelf(); - - $this->quoteAddressMock->expects($this->once())->method('setSameAsBilling')->with(1)->willReturnSelf(); - $this->quoteAddressMock->expects($this->once())->method('setSaveInAddressBook')->with(1)->willReturnSelf(); - $this->quoteAddressMock->expects($this->once()) - ->method('setCollectShippingRates') - ->with(true) - ->willReturnSelf(); - - $this->service->assign('cart867', $this->quoteAddressMock); - } - - public function testGetAddress() - { - $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); - $this->quoteRepositoryMock->expects($this->once())->method('getActive')->with('cartId')->will( - $this->returnValue($quoteMock) - ); - - $addressMock = $this->createMock(\Magento\Quote\Model\Quote\Address::class); - $quoteMock->expects($this->any())->method('getShippingAddress')->will($this->returnValue($addressMock)); - $quoteMock->expects($this->any())->method('isVirtual')->will($this->returnValue(false)); - $this->assertEquals($addressMock, $this->service->get('cartId')); - } - - /** - * @expectedException \Exception - * @expectedExceptionMessage The Cart includes virtual product(s) only, so a shipping address is not used. - */ - public function testGetAddressOfQuoteWithVirtualProducts() - { - $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class); - $this->quoteRepositoryMock->expects($this->once())->method('getActive')->with('cartId')->will( - $this->returnValue($quoteMock) - ); - - $quoteMock->expects($this->any())->method('isVirtual')->will($this->returnValue(true)); - $quoteMock->expects($this->never())->method('getShippingAddress'); - - $this->service->get('cartId'); - } -} From 7b81627e2be1da1db25b2e44d3d0fe0ab59b9e67 Mon Sep 17 00:00:00 2001 From: roman Date: Mon, 17 Dec 2018 16:36:28 +0200 Subject: [PATCH 083/275] MAGETWO-95439: Fixed incorrect request flow for sitemaps --- .../Sitemap/Controller/Adminhtml/Sitemap/Delete.php | 6 +++++- .../Sitemap/Controller/Adminhtml/Sitemap/Edit.php | 9 +++++++-- .../Sitemap/Controller/Adminhtml/Sitemap/Generate.php | 8 ++++++-- .../Sitemap/Controller/Adminhtml/Sitemap/Save.php | 6 +++++- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Delete.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Delete.php index 29d50ea8408fd..1c807cbfc194e 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Delete.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Delete.php @@ -5,10 +5,14 @@ */ namespace Magento\Sitemap\Controller\Adminhtml\Sitemap; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\App\ObjectManager; -class Delete extends \Magento\Sitemap\Controller\Adminhtml\Sitemap +/** + * Controller class Delete. Represents adminhtml request flow for a sitemap deletion + */ +class Delete extends \Magento\Sitemap\Controller\Adminhtml\Sitemap implements HttpPostActionInterface { /** * @var \Magento\Framework\Filesystem diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php index 111353550b9cd..14771e7f03a3b 100644 --- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php +++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Edit.php @@ -1,12 +1,17 @@ Date: Tue, 22 Jan 2019 09:18:22 +0200 Subject: [PATCH 084/275] MC-5845: Wrong behavior of Delete button on backend --- app/code/Magento/Backend/Block/Widget/Form/Container.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/code/Magento/Backend/Block/Widget/Form/Container.php b/app/code/Magento/Backend/Block/Widget/Form/Container.php index 97116de6db79b..aa8613310689d 100644 --- a/app/code/Magento/Backend/Block/Widget/Form/Container.php +++ b/app/code/Magento/Backend/Block/Widget/Form/Container.php @@ -83,7 +83,7 @@ protected function _construct() -1 ); - $objId = $this->getRequest()->getParam($this->_objectId); + $objId = (int)$this->getRequest()->getParam($this->_objectId); if (!empty($objId)) { $this->addButton( @@ -155,7 +155,7 @@ public function getBackUrl() */ public function getDeleteUrl() { - return $this->getUrl('*/*/delete', [$this->_objectId => $this->getRequest()->getParam($this->_objectId)]); + return $this->getUrl('*/*/delete', [$this->_objectId => (int)$this->getRequest()->getParam($this->_objectId)]); } /** From 10162447a958cc2e2ec653e6ddbf856413aefb11 Mon Sep 17 00:00:00 2001 From: DianaRusin Date: Tue, 22 Jan 2019 11:55:44 +0200 Subject: [PATCH 085/275] MC-5845: Wrong behavior of Delete button on backend --- .../Backend/Block/Widget/Form/Container.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/code/Magento/Backend/Block/Widget/Form/Container.php b/app/code/Magento/Backend/Block/Widget/Form/Container.php index aa8613310689d..febaae3861688 100644 --- a/app/code/Magento/Backend/Block/Widget/Form/Container.php +++ b/app/code/Magento/Backend/Block/Widget/Form/Container.php @@ -56,6 +56,8 @@ class Container extends \Magento\Backend\Block\Widget\Container protected $_template = 'Magento_Backend::widget/form/container.phtml'; /** + * Initialize form. + * * @return void */ protected function _construct() @@ -151,6 +153,8 @@ public function getBackUrl() } /** + * Get URL for delete button. + * * @return string */ public function getDeleteUrl() @@ -183,6 +187,8 @@ public function getFormActionUrl() } /** + * Get form HTML. + * * @return string */ public function getFormHtml() @@ -192,6 +198,8 @@ public function getFormHtml() } /** + * Get form init scripts. + * * @return string */ public function getFormInitScripts() @@ -203,6 +211,8 @@ public function getFormInitScripts() } /** + * Get form scripts. + * * @return string */ public function getFormScripts() @@ -214,6 +224,8 @@ public function getFormScripts() } /** + * Get header width. + * * @return string */ public function getHeaderWidth() @@ -222,6 +234,8 @@ public function getHeaderWidth() } /** + * Get header css class. + * * @return string */ public function getHeaderCssClass() @@ -230,6 +244,8 @@ public function getHeaderCssClass() } /** + * Get header HTML. + * * @return string */ public function getHeaderHtml() From 3149789b28c38488b2da7b98188718de7a3f4499 Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Tue, 22 Jan 2019 14:01:13 -0600 Subject: [PATCH 086/275] MC-10866: Cart's customer and address mismatch --- app/code/Magento/Quote/Model/QuoteAddressValidator.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/code/Magento/Quote/Model/QuoteAddressValidator.php b/app/code/Magento/Quote/Model/QuoteAddressValidator.php index 50807f71a29d0..e7750f5879de5 100644 --- a/app/code/Magento/Quote/Model/QuoteAddressValidator.php +++ b/app/code/Magento/Quote/Model/QuoteAddressValidator.php @@ -11,6 +11,8 @@ /** * Quote shipping/billing address validator service. + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class QuoteAddressValidator { @@ -29,7 +31,7 @@ class QuoteAddressValidator protected $customerRepository; /** - * @var \Magento\Customer\Model\Session + * @deprecated This class is not a part of HTML presentation layer and should not use sessions. */ protected $customerSession; From a9e69f7ba86aebd0c483eab045748f83b4f92235 Mon Sep 17 00:00:00 2001 From: Oleksandr Gorkun Date: Tue, 22 Jan 2019 18:08:08 -0600 Subject: [PATCH 087/275] MC-11058: Empty results when using methods in templates --- lib/internal/Magento/Framework/Filter/Template.php | 5 ++++- .../Framework/Filter/Test/Unit/TemplateTest.php | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/internal/Magento/Framework/Filter/Template.php b/lib/internal/Magento/Framework/Filter/Template.php index 3e5f9bcf0bd27..9d3449e79c254 100644 --- a/lib/internal/Magento/Framework/Filter/Template.php +++ b/lib/internal/Magento/Framework/Filter/Template.php @@ -412,7 +412,10 @@ protected function getVariable($value, $default = '{no_value_defined}') } } $last = $i; - } elseif (isset($stackVars[$i - 1]['variable']) && $stackVars[$i]['type'] == 'method') { + } elseif (isset($stackVars[$i - 1]['variable']) + && is_object($stackVars[$i - 1]['variable']) + && $stackVars[$i]['type'] == 'method' + ) { // Calling object methods if (method_exists($stackVars[$i - 1]['variable'], $stackVars[$i]['name'])) { $stackVars[$i]['args'] = $this->getStackArgs($stackVars[$i]['args']); diff --git a/lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php b/lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php index 4883dc5fbe33b..6fd48fa951d09 100644 --- a/lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php +++ b/lib/internal/Magento/Framework/Filter/Test/Unit/TemplateTest.php @@ -380,4 +380,16 @@ private function getObjectData() $dataObject->setAllVisibleItems($visibleItems); return $dataObject; } + + /** + * Check that if calling a method of an object fails expected result is returned. + */ + public function testInvalidMethodCall() + { + $this->templateFilter->setVariables(['dateTime' => '\DateTime']); + $this->assertEquals( + '\DateTime', + $this->templateFilter->filter('{{var dateTime.createFromFormat(\'d\',\'1548201468\')}}') + ); + } } From 0626c9de27c32f8d8a2efe2d0943d38dfcc595f5 Mon Sep 17 00:00:00 2001 From: OlgaVasyltsun Date: Wed, 23 Jan 2019 10:16:54 +0200 Subject: [PATCH 088/275] MC-10864: Media attributes changes --- .../Model/Config/CatalogClone/Media/Image.php | 18 +++- .../Config/CatalogClone/Media/ImageTest.php | 84 ++++++++++++++----- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php b/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php index e2b0a91574021..10675a7b7c7e2 100644 --- a/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php +++ b/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php @@ -5,6 +5,9 @@ */ namespace Magento\Catalog\Model\Config\CatalogClone\Media; +use Magento\Framework\Escaper; +use Magento\Framework\App\ObjectManager; + /** * Clone model for media images related config fields * @@ -26,6 +29,11 @@ class Image extends \Magento\Framework\App\Config\Value */ protected $_attributeCollectionFactory; + /** + * @var Escaper + */ + private $escaper; + /** * @param \Magento\Framework\Model\Context $context * @param \Magento\Framework\Registry $registry @@ -36,6 +44,9 @@ class Image extends \Magento\Framework\App\Config\Value * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection * @param array $data + * @param Escaper|null $escaper + * + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( \Magento\Framework\Model\Context $context, @@ -46,8 +57,10 @@ public function __construct( \Magento\Eav\Model\Config $eavConfig, \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null, \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null, - array $data = [] + array $data = [], + Escaper $escaper = null ) { + $this->escaper = $escaper ?? ObjectManager::getInstance()->get(Escaper::class); $this->_attributeCollectionFactory = $attributeCollectionFactory; $this->_eavConfig = $eavConfig; parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data); @@ -71,10 +84,9 @@ public function getPrefixes() $prefixes = []; foreach ($collection as $attribute) { - /* @var $attribute \Magento\Eav\Model\Entity\Attribute */ $prefixes[] = [ 'field' => $attribute->getAttributeCode() . '_', - 'label' => $attribute->getFrontend()->getLabel(), + 'label' => $this->escaper->escapeHtml($attribute->getFrontend()->getLabel()), ]; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php index 5b1d3bf7943fc..23f0aec5b69a2 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php @@ -9,6 +9,11 @@ use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; +/** + * Tests \Magento\Catalog\Model\Config\CatalogClone\Media\Image. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class ImageTest extends \PHPUnit\Framework\TestCase { /** @@ -36,6 +41,14 @@ class ImageTest extends \PHPUnit\Framework\TestCase */ private $attribute; + /** + * @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject + */ + private $escaperMock; + + /** + * @inheritdoc + */ protected function setUp() { $this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class) @@ -62,54 +75,79 @@ protected function setUp() ->disableOriginalConstructor() ->getMock(); + $this->escaperMock = $this->getMockBuilder( + \Magento\Framework\Escaper::class + ) + ->disableOriginalConstructor() + ->setMethods(['escapeHtml']) + ->getMock(); + $helper = new ObjectManager($this); $this->model = $helper->getObject( \Magento\Catalog\Model\Config\CatalogClone\Media\Image::class, [ 'eavConfig' => $this->eavConfig, - 'attributeCollectionFactory' => $this->attributeCollectionFactory + 'attributeCollectionFactory' => $this->attributeCollectionFactory, + 'escaper' => $this->escaperMock, ] ); } - public function testGetPrefixes() + /** + * @param string $actualLabel + * @param string $expectedLabel + * @return void + * + * @dataProvider getPrefixesDataProvider + */ + public function testGetPrefixes(string $actualLabel, string $expectedLabel): void { $entityTypeId = 3; /** @var \Magento\Eav\Model\Entity\Type|\PHPUnit_Framework_MockObject_MockObject $entityType */ $entityType = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class) ->disableOriginalConstructor() ->getMock(); - $entityType->expects($this->once())->method('getId')->will($this->returnValue($entityTypeId)); + $entityType->expects($this->once())->method('getId')->willReturn($entityTypeId); /** @var AbstractFrontend|\PHPUnit_Framework_MockObject_MockObject $frontend */ $frontend = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend::class) ->setMethods(['getLabel']) ->disableOriginalConstructor() ->getMockForAbstractClass(); - $frontend->expects($this->once())->method('getLabel')->will($this->returnValue('testLabel')); + $frontend->expects($this->once())->method('getLabel')->willReturn($actualLabel); - $this->attributeCollection->expects($this->once())->method('setEntityTypeFilter')->with( - $this->equalTo($entityTypeId) - ); - $this->attributeCollection->expects($this->once())->method('setFrontendInputTypeFilter')->with( - $this->equalTo('media_image') - ); + $this->attributeCollection->expects($this->once())->method('setEntityTypeFilter')->with($entityTypeId); + $this->attributeCollection->expects($this->once())->method('setFrontendInputTypeFilter')->with('media_image'); - $this->attribute->expects($this->once())->method('getAttributeCode')->will( - $this->returnValue('attributeCode') - ); - $this->attribute->expects($this->once())->method('getFrontend')->will( - $this->returnValue($frontend) - ); + $this->attribute->expects($this->once())->method('getAttributeCode')->willReturn('attributeCode'); + $this->attribute->expects($this->once())->method('getFrontend')->willReturn($frontend); - $this->attributeCollection->expects($this->any())->method('getIterator')->will( - $this->returnValue(new \ArrayIterator([$this->attribute])) - ); + $this->attributeCollection->expects($this->any())->method('getIterator') + ->willReturn(new \ArrayIterator([$this->attribute])); + + $this->eavConfig->expects($this->any())->method('getEntityType')->with(Product::ENTITY) + ->willReturn($entityType); - $this->eavConfig->expects($this->any())->method('getEntityType')->with( - $this->equalTo(Product::ENTITY) - )->will($this->returnValue($entityType)); + $this->escaperMock->expects($this->once())->method('escapeHtml')->with($actualLabel) + ->willReturn($expectedLabel); - $this->assertEquals([['field' => 'attributeCode_', 'label' => 'testLabel']], $this->model->getPrefixes()); + $this->assertEquals([['field' => 'attributeCode_', 'label' => $expectedLabel]], $this->model->getPrefixes()); + } + + /** + * @return array + */ + public function getPrefixesDataProvider(): array + { + return [ + [ + 'actual_label' => 'testLabel', + 'expected_label' => 'testLabel', + ], + [ + 'actual_label' => ' '<media-image-attributelabel', + ], + ]; } } From f5f137c718089f709949ab269454ddc1f925da90 Mon Sep 17 00:00:00 2001 From: Andrii Meysar Date: Wed, 23 Jan 2019 13:06:23 +0200 Subject: [PATCH 089/275] MC-10852: Attribute output --- .../catalog/product/composite/fieldset/configurable.phtml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml index a8712cdc183de..a6e58afd312ef 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml @@ -20,9 +20,8 @@
-