diff --git a/.htaccess b/.htaccess index d22b5a1395cae..71a5cf708dbc5 100644 --- a/.htaccess +++ b/.htaccess @@ -27,6 +27,11 @@ #AddType x-mapp-php5 .php #AddHandler x-mapp-php5 .php +############################################ +## enable usage of methods arguments in backtrace + + SetEnv MAGE_DEBUG_SHOW_ARGS 1 + ############################################ ## default index file @@ -364,6 +369,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..c9e83a53cc8bd 100644 --- a/.htaccess.sample +++ b/.htaccess.sample @@ -27,6 +27,11 @@ #AddType x-mapp-php5 .php #AddHandler x-mapp-php5 .php +############################################ +## enable usage of methods arguments in backtrace + + SetEnv MAGE_DEBUG_SHOW_ARGS 1 + ############################################ ## default index file @@ -341,6 +346,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/app/bootstrap.php b/app/bootstrap.php index 0b13d12cece58..ddbcaffd42962 100644 --- a/app/bootstrap.php +++ b/app/bootstrap.php @@ -8,6 +8,7 @@ * Environment initialization */ error_reporting(E_ALL); +stream_wrapper_unregister('phar'); #ini_set('display_errors', 1); /* PHP version validation */ diff --git a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php index 6f0e42bdcbef1..82f70d92e4930 100644 --- a/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php +++ b/app/code/Magento/AdminNotification/Block/Grid/Renderer/Actions.php @@ -8,6 +8,11 @@ namespace Magento\AdminNotification\Block\Grid\Renderer; +/** + * Renderer class for action in the admin notifications grid + * + * @package Magento\AdminNotification\Block\Grid\Renderer + */ class Actions extends \Magento\Backend\Block\Widget\Grid\Column\Renderer\AbstractRenderer { /** @@ -37,7 +42,9 @@ public function __construct( */ public function render(\Magento\Framework\DataObject $row) { - $readDetailsHtml = $row->getUrl() ? '' . + $readDetailsHtml = $row->getUrl() ? '' . __('Read Details') . '' : ''; $markAsReadHtml = !$row->getIsRead() ? '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/Type/Select/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php new file mode 100644 index 0000000000000..3d856f85dbd94 --- /dev/null +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php @@ -0,0 +1,68 @@ + $value->getPriceType() === 'percent', + 'pricing_value' => $value->getPrice($value->getPriceType() === 'percent') + ] + ); + } + + /** + * Returns current currency for store + * + * @param ProductCustomOptionValuesInterface $value + * @return float|string + */ + public function getCurrencyByStore(ProductCustomOptionValuesInterface $value) + { + /** @noinspection PhpMethodParametersCountMismatchInspection */ + return $this->pricingHelper->currencyByStore( + $value->getPrice(true), + $this->getProduct()->getStore(), + false + ); + } + + /** + * Returns preconfigured value for given option + * + * @param Option $option + * @return string|array|null + */ + public function getPreconfiguredValue(Option $option) + { + return $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId()); + } +} 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 new file mode 100644 index 0000000000000..09a931dfa0693 --- /dev/null +++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php @@ -0,0 +1,112 @@ +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(); + } + + /** + * Returns select with inserted option give as a parameter + * + * @param Select $select + * @param Option $option + * @return Select + */ + private function insertSelectOption(Select $select, Option $option): Select + { + $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; + } + + /** + * Returns select with formated option prices + * + * @param Select $select + * @param Option $option + * @return Select + */ + private function processSelectOption(Select $select, Option $option): Select + { + $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/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/Model/Product/ProductFrontendAction/Synchronizer.php b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php index 3ec8e968aa245..24775a791e59f 100644 --- a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php +++ b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php @@ -16,6 +16,8 @@ use Magento\Framework\EntityManager\EntityManager; /** + * A Product Widget Synchronizer. + * * Service which allows to sync product widget information, such as product id with db. In order to reuse this info * on different devices */ @@ -85,9 +87,10 @@ public function __construct( } /** - * Find lifetime in configuration. Configuration is hold in Stores Configuration - * Also this configuration is generated by: - * @see \Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration + * Finds lifetime in configuration. + * + * Configuration is hold in Stores Configuration. Also this configuration is generated by + * {@see Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration} * * @param string $namespace * @return int @@ -108,6 +111,8 @@ private function getLifeTimeByNamespace($namespace) } /** + * Filters actions. + * * In order to avoid suspicious actions, we need to filter them in DESC order, and slice only items that * can be persisted in database. * @@ -138,7 +143,9 @@ private function getProductIdsByActions(array $actions) $productIds = []; foreach ($actions as $action) { - $productIds[] = $action['product_id']; + if (isset($action['product_id'])) { + $productIds[] = $action['product_id']; + } } return $productIds; @@ -159,33 +166,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); + } } } diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml index 37fbf01a6b9aa..8149bc34087fb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml @@ -109,6 +109,7 @@ + @@ -120,6 +121,7 @@ + @@ -131,6 +133,7 @@ + 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', + ], + ]; } } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php index fce4a02622d9e..38bed83cb9504 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php @@ -80,6 +80,7 @@ protected function setUp() public function testFilterProductActions() { + $typeId = 'recently_compared_product'; $productsData = [ 1 => [ 'added_at' => 12, @@ -87,7 +88,7 @@ public function testFilterProductActions() ], 2 => [ 'added_at' => 13, - 'product_id' => 2, + 'product_id' => '2', ], 3 => [ 'added_at' => 14, @@ -126,10 +127,12 @@ public function testFilterProductActions() $collection->expects($this->once()) ->method('addFilterByUserIdentities') ->with(1, 34); - $collection->expects($this->any()) + $collection->expects($this->at(1)) ->method('addFieldToFilter') - ->withConsecutive(['type_id'], ['product_id']); - + ->with('type_id', $typeId); + $collection->expects($this->at(2)) + ->method('addFieldToFilter') + ->with('product_id', [1, 2]); $iterator = new \IteratorIterator(new \ArrayIterator([$frontendAction])); $collection->expects($this->once()) ->method('getIterator') 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..0f3b4f481a288 --- /dev/null +++ b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml @@ -0,0 +1,100 @@ +getOption(); +if ($option) : ?> + getPreconfiguredValue($option); + $optionType = $option->getType(); + $arraySign = $optionType === Option::OPTION_TYPE_CHECKBOX ? '[]' : ''; + $count = 1; + ?> + +
+ getIsRequire()): ?> +
+ + +
+ + + 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 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 c96df9cdd3195..454031279d882 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/CheckoutAgreements/Model/AgreementsConfigProvider.php b/app/code/Magento/CheckoutAgreements/Model/AgreementsConfigProvider.php index 1f1b5be9683ed..1217270d780e1 100644 --- a/app/code/Magento/CheckoutAgreements/Model/AgreementsConfigProvider.php +++ b/app/code/Magento/CheckoutAgreements/Model/AgreementsConfigProvider.php @@ -67,17 +67,18 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritdoc */ public function getConfig() { $agreements = []; $agreements['checkoutAgreements'] = $this->getAgreementsConfig(); + return $agreements; } /** - * Returns agreements config + * Returns agreements config. * * @return array */ @@ -99,7 +100,7 @@ protected function getAgreementsConfig() 'content' => $agreement->getIsHtml() ? $agreement->getContent() : nl2br($this->escaper->escapeHtml($agreement->getContent())), - 'checkboxText' => $agreement->getCheckboxText(), + 'checkboxText' => $this->escaper->escapeHtml($agreement->getCheckboxText()), 'mode' => $agreement->getMode(), 'agreementId' => $agreement->getAgreementId() ]; diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/AgreementsConfigProviderTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/AgreementsConfigProviderTest.php index c59a3d2433ec2..c8309bacb0a86 100644 --- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/AgreementsConfigProviderTest.php +++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/AgreementsConfigProviderTest.php @@ -8,6 +8,9 @@ use Magento\CheckoutAgreements\Model\AgreementsProvider; use Magento\Store\Model\ScopeInterface; +/** + * Tests for AgreementsConfigProvider. + */ class AgreementsConfigProviderTest extends \PHPUnit\Framework\TestCase { /** @@ -35,6 +38,9 @@ class AgreementsConfigProviderTest extends \PHPUnit\Framework\TestCase */ private $agreementsFilterMock; + /** + * @inheritdoc + */ protected function setUp() { $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class); @@ -59,10 +65,16 @@ protected function setUp() ); } + /** + * Test for getConfig if content is HTML. + * + * @return void + */ public function testGetConfigIfContentIsHtml() { $content = 'content'; $checkboxText = 'checkbox_text'; + $escapedCheckboxText = 'escaped_checkbox_text'; $mode = \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_AUTO; $agreementId = 100; $expectedResult = [ @@ -71,12 +83,12 @@ public function testGetConfigIfContentIsHtml() 'agreements' => [ [ 'content' => $content, - 'checkboxText' => $checkboxText, + 'checkboxText' => $escapedCheckboxText, 'mode' => $mode, - 'agreementId' => $agreementId - ] - ] - ] + 'agreementId' => $agreementId, + ], + ], + ], ]; $this->scopeConfigMock->expects($this->once()) @@ -94,6 +106,11 @@ public function testGetConfigIfContentIsHtml() ->with($searchCriteriaMock) ->willReturn([$agreement]); + $this->escaperMock->expects($this->once()) + ->method('escapeHtml') + ->with($checkboxText) + ->willReturn($escapedCheckboxText); + $agreement->expects($this->once())->method('getIsHtml')->willReturn(true); $agreement->expects($this->once())->method('getContent')->willReturn($content); $agreement->expects($this->once())->method('getCheckboxText')->willReturn($checkboxText); @@ -103,11 +120,17 @@ public function testGetConfigIfContentIsHtml() $this->assertEquals($expectedResult, $this->model->getConfig()); } + /** + * Test for getConfig if content is not HTML. + * + * @return void + */ public function testGetConfigIfContentIsNotHtml() { $content = 'content'; $escapedContent = 'escaped_content'; $checkboxText = 'checkbox_text'; + $escapedCheckboxText = 'escaped_checkbox_text'; $mode = \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_AUTO; $agreementId = 100; $expectedResult = [ @@ -116,12 +139,12 @@ public function testGetConfigIfContentIsNotHtml() 'agreements' => [ [ 'content' => $escapedContent, - 'checkboxText' => $checkboxText, + 'checkboxText' => $escapedCheckboxText, 'mode' => $mode, - 'agreementId' => $agreementId - ] - ] - ] + 'agreementId' => $agreementId, + ], + ], + ], ]; $this->scopeConfigMock->expects($this->once()) @@ -139,8 +162,11 @@ public function testGetConfigIfContentIsNotHtml() ->with($searchCriteriaMock) ->willReturn([$agreement]); - $this->escaperMock->expects($this->once())->method('escapeHtml')->with($content)->willReturn($escapedContent); - + $this->escaperMock->expects($this->at(0))->method('escapeHtml')->with($content)->willReturn($escapedContent); + $this->escaperMock->expects($this->at(1)) + ->method('escapeHtml') + ->with($checkboxText) + ->willReturn($escapedCheckboxText); $agreement->expects($this->once())->method('getIsHtml')->willReturn(false); $agreement->expects($this->once())->method('getContent')->willReturn($content); $agreement->expects($this->once())->method('getCheckboxText')->willReturn($checkboxText); diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/NewFolder.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/NewFolder.php index a7f49e8a431a4..82d200beb6dc9 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/NewFolder.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/NewFolder.php @@ -6,12 +6,13 @@ */ namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; /** * Creates new folder. */ -class NewFolder extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images +class NewFolder extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images implements HttpPostActionInterface { /** * @var \Magento\Framework\Controller\Result\JsonFactory 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 31b01ce115c21..9bad371aa84d7 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\Action\HttpPostActionInterface; @@ -58,13 +61,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 ca563bd9d8f61..dfbbce99b6515 100644 --- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php @@ -3,17 +3,24 @@ * 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; use Magento\Framework\App\Filesystem\DirectoryList; /** - * Wysiwyg Images model + * Wysiwyg Images model. + * + * Tightly connected with controllers responsible for managing files so it uses session and is (sort of) a part + * of the presentation layer. * * @SuppressWarnings(PHPMD.LongVariable) * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) * * @api * @since 100.0.2 diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/FileTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/FileTest.php index de18d45d26864..31215f1bdee2b 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/FileTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/FileTest.php @@ -40,7 +40,11 @@ protected function setUp() $this->file = $objectManager->getObject( \Magento\Config\Block\System\Config\Form\Field\File::class, - ['data' => $this->testData] + [ + '_escaper' => $objectManager->getObject(\Magento\Framework\Escaper::class), + 'data' => $this->testData, + + ] ); $formMock = new \Magento\Framework\DataObject(); diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/ImageTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/ImageTest.php index 8a005a52ab614..b752f79f73446 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/ImageTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/ImageTest.php @@ -34,6 +34,7 @@ protected function setUp() \Magento\Config\Block\System\Config\Form\Field\Image::class, [ 'urlBuilder' => $this->urlBuilderMock, + '_escaper' => $objectManager->getObject(\Magento\Framework\Escaper::class) ] ); diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/Select/AllowspecificTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/Select/AllowspecificTest.php index f5c65e848b3bf..e7ba2e8aaa2e7 100644 --- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/Select/AllowspecificTest.php +++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/Select/AllowspecificTest.php @@ -21,7 +21,10 @@ protected function setUp() { $testHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->_object = $testHelper->getObject( - \Magento\Config\Block\System\Config\Form\Field\Select\Allowspecific::class + \Magento\Config\Block\System\Config\Form\Field\Select\Allowspecific::class, + [ + '_escaper' => $testHelper->getObject(\Magento\Framework\Escaper::class) + ] ); $this->_object->setData('html_id', 'spec_element'); $this->_formMock = $this->createPartialMock( 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 190ecccbfdb76..ecc95cbe3d48f 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 @@
-
- + getChildHtml('preview_form') ?>
diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Block/Adminhtml/Form/Field/ImportTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Block/Adminhtml/Form/Field/ImportTest.php index 8d75cc32914b4..a1fb2e449d7bf 100644 --- a/app/code/Magento/OfflineShipping/Test/Unit/Block/Adminhtml/Form/Field/ImportTest.php +++ b/app/code/Magento/OfflineShipping/Test/Unit/Block/Adminhtml/Form/Field/ImportTest.php @@ -33,7 +33,10 @@ protected function setUp() $testHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); $this->_object = $testHelper->getObject( \Magento\OfflineShipping\Block\Adminhtml\Form\Field\Import::class, - ['data' => $testData] + [ + 'data' => $testData, + '_escaper' => $testHelper->getObject(\Magento\Framework\Escaper::class) + ] ); $this->_object->setForm($this->_formMock); } diff --git a/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Field/Enable/AbstractEnableTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Field/Enable/AbstractEnableTest.php index b33d2f5723961..b9ea53c154014 100644 --- a/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Field/Enable/AbstractEnableTest.php +++ b/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Field/Enable/AbstractEnableTest.php @@ -5,6 +5,8 @@ */ namespace Magento\Paypal\Test\Unit\Block\Adminhtml\System\Config\Field\Enable; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; + /** * Class AbstractEnableTest * @@ -43,8 +45,18 @@ protected function setUp() )->disableOriginalConstructor() ->getMockForAbstractClass(); + $objectManager = new ObjectManager($this); + $escaper = $objectManager->getObject(\Magento\Framework\Escaper::class); + $reflection = new \ReflectionClass($this->elementMock); + $reflection_property = $reflection->getProperty('_escaper'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($this->elementMock, $escaper); + $this->abstractEnable = $objectManager->getObject( - \Magento\Paypal\Test\Unit\Block\Adminhtml\System\Config\Field\Enable\AbstractEnable\Stub::class + \Magento\Paypal\Test\Unit\Block\Adminhtml\System\Config\Field\Enable\AbstractEnable\Stub::class, + [ + '_escaper' => $objectManager->getObject(\Magento\Framework\Escaper::class) + ] ); } diff --git a/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php b/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php index c5b8dc1c4b124..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 @@ -47,7 +52,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..e7750f5879de5 100644 --- a/app/code/Magento/Quote/Model/QuoteAddressValidator.php +++ b/app/code/Magento/Quote/Model/QuoteAddressValidator.php @@ -6,10 +6,13 @@ 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. * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class QuoteAddressValidator { @@ -28,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; @@ -50,44 +53,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..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) @@ -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(); @@ -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'); - } -} 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 = ' 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/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 4922a9f365ced..3e00353a9157d 100644 --- a/app/code/Magento/SendFriend/view/frontend/templates/send.phtml +++ b/app/code/Magento/SendFriend/view/frontend/templates/send.phtml @@ -108,6 +108,7 @@ getChildHtml('form_additional_info') ?> + getChildHtml('captcha'); ?>
+ getChildHtml('captcha'); ?>