diff --git a/CHANGELOG.md b/CHANGELOG.md
index cc722b6d61b..b86c7b79a0c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -157,7 +157,7 @@ To get detailed information about changes in Magento 2.1.0, please visit [Magent
* Updated styles
* Sample Data:
* Improved sample data installation UX
- * Updated sample data with Product Heros, color swatches, MAP and rule based product relations
+ * Updated sample data with Product Heroes, color swatches, MAP and rule based product relations
* Improved sample data upgrade flow
* Added the ability to log errors and set the error flag during sample data installation
* Various improvements:
@@ -2284,7 +2284,7 @@ Tests:
* Fixed an issue where no results were found for Coupons reports
* Fixed an issue with incremental Qty setting
* Fixed an issue with allowing importing of negative weight values
- * Fixed an issue with Inventory - Only X left Treshold being not dependent on Qty for Item's Status to Become Out of Stock
+ * Fixed an issue with Inventory - Only X left Threshold being not dependent on Qty for Item's Status to Become Out of Stock
* Fixed an issue where the "Catalog Search Index index was rebuilt." message was displayed when reindexing the Catalog Search index
* Search module:
* Integrated the Search library to the advanced search functionality
@@ -2706,7 +2706,7 @@ Tests:
* Ability to support extensible service data objects
* No Code Duplication in Root Templates
* Fixed bugs:
- * Persistance session application. Loggin out the customer
+ * Persistence session application. Logging out the customer
* Placing the order with two terms and conditions
* Saving of custom option by service catalogProductCustomOptionsWriteServiceV1
* Placing the order on frontend if enter in the street address line 1 and 2 255 symbols
@@ -2965,7 +2965,7 @@ Tests:
* Fixed an issue with incorrect items label for the cases when there are more than one item in the category
* Fixed an issue when configurable product was out of stock in Google Shopping while being in stock in the Magento backend
* Fixed an issue when swipe gesture in menu widget was not supported on mobile
- * Fixed an issue when it was impossible to enter alpha-numeric zip code on the stage of estimating shipping and tax rates
+ * Fixed an issue when it was impossible to enter alphanumeric zip code on the stage of estimating shipping and tax rates
* Fixed an issue when custom price was not applied when editing an order
* Fixed an issue when items were not returned to stock after unsuccessful order was placed
* Fixed an issue when error message appeared "Cannot save the credit memo” while creating credit memo
diff --git a/README.md b/README.md
index c292f1100f3..979e60a1677 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,11 @@
Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting-edge, feature-rich eCommerce solution that gets results.
## Magento system requirements
-[Magento system requirements](http://devdocs.magento.com/guides/v2.3/install-gde/system-requirements2.html).
+[Magento system requirements](https://devdocs.magento.com/guides/v2.3/install-gde/system-requirements2.html).
## Install Magento
-* [Installation guide](http://devdocs.magento.com/guides/v2.3/install-gde/bk-install-guide.html).
+* [Installation guide](https://devdocs.magento.com/guides/v2.3/install-gde/bk-install-guide.html).
Contributing to the Magento 2 code base
Contributions can take the form of new components or features, changes to existing features, tests, documentation (such as developer guides, user guides, examples, or specifications), bug fixes, optimizations, or just good suggestions.
@@ -21,10 +21,10 @@ To learn about issues, click [here][2]. To open an issue, click [here][3].
To suggest documentation improvements, click [here][4].
-[1]:
-[2]:
+[1]:
+[2]:
[3]:
-[4]:
+[4]:
Community Maintainers
The members of this team have been recognized for their outstanding commitment to maintaining and improving Magento. Magento has granted them permission to accept, merge, and reject pull requests, as well as review issues, and thanks these Community Maintainers for their valuable contributions.
@@ -53,9 +53,9 @@ Stay up-to-date on the latest security news and patches for Magento by signing u
Each Magento source file included in this distribution is licensed under OSL 3.0 or the Magento Enterprise Edition (MEE) license.
-http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
-Please see LICENSE.txt for the full text of the OSL 3.0 license or contact license@magentocommerce.com for a copy.
+[Open Software License (OSL 3.0)](https://opensource.org/licenses/osl-3.0.php).
+Please see [LICENSE.txt](https://github.com/magento/magento2/blob/2.3-develop/LICENSE.txt) for the full text of the OSL 3.0 license or contact license@magentocommerce.com for a copy.
Subject to Licensee's payment of fees and compliance with the terms and conditions of the MEE License, the MEE License supersedes the OSL 3.0 license for each source file.
-Please see LICENSE_EE.txt for the full text of the MEE License or visit http://magento.com/legal/terms/enterprise.
+Please see LICENSE_EE.txt for the full text of the MEE License or visit https://magento.com/legal/terms/enterprise.
diff --git a/app/code/Magento/AdminNotification/Block/Window.php b/app/code/Magento/AdminNotification/Block/Window.php
index 9563626ee25..e9b4bfa4489 100644
--- a/app/code/Magento/AdminNotification/Block/Window.php
+++ b/app/code/Magento/AdminNotification/Block/Window.php
@@ -8,6 +8,8 @@
namespace Magento\AdminNotification\Block;
/**
+ * Admin notification window block
+ *
* @api
* @since 100.0.2
*/
@@ -40,7 +42,7 @@ class Window extends \Magento\Backend\Block\Template
protected $_criticalCollection;
/**
- * @var \Magento\Adminnotification\Model\Inbox
+ * @var \Magento\AdminNotification\Model\Inbox
*/
protected $_latestItem;
@@ -92,7 +94,7 @@ protected function _toHtml()
/**
* Retrieve latest critical item
*
- * @return bool|\Magento\Adminnotification\Model\Inbox
+ * @return bool|\Magento\AdminNotification\Model\Inbox
*/
protected function _getLatestItem()
{
diff --git a/app/code/Magento/AdminNotification/etc/db_schema.xml b/app/code/Magento/AdminNotification/etc/db_schema.xml
index 35e6045b607..29d928ced20 100644
--- a/app/code/Magento/AdminNotification/etc/db_schema.xml
+++ b/app/code/Magento/AdminNotification/etc/db_schema.xml
@@ -21,16 +21,16 @@
default="0" comment="Flag if notification read"/>
-
+
-
+
-
+
-
+
@@ -40,7 +40,7 @@
default="0" comment="Problem type"/>
-
+
diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php
index b46e286e750..d78c4f5e61a 100644
--- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php
+++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricing/Validator/WebsiteTest.php
@@ -103,13 +103,13 @@ public function testGetAllWebsitesValue()
$this->webSiteModel->expects($this->once())->method('getBaseCurrency')->willReturn($currency);
$expectedResult = AdvancedPricing::VALUE_ALL_WEBSITES . ' [' . $currencyCode . ']';
- $this->websiteString = $this->getMockBuilder(
+ $websiteString = $this->getMockBuilder(
\Magento\AdvancedPricingImportExport\Model\Import\AdvancedPricing\Validator\Website::class
)
->setMethods(['_clearMessages', '_addMessages'])
->setConstructorArgs([$this->storeResolver, $this->webSiteModel])
->getMock();
- $result = $this->websiteString->getAllWebsitesValue();
+ $result = $websiteString->getAllWebsitesValue();
$this->assertEquals($expectedResult, $result);
}
diff --git a/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php b/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php
index 546983bb5e5..c0c224766eb 100644
--- a/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php
+++ b/app/code/Magento/AdvancedSearch/Model/Recommendations/DataProvider.php
@@ -10,6 +10,9 @@
use Magento\Search\Model\QueryInterface;
use Magento\AdvancedSearch\Model\SuggestedQueriesInterface;
+/**
+ * Class DataProvider
+ */
class DataProvider implements SuggestedQueriesInterface
{
/**
@@ -51,6 +54,8 @@ class DataProvider implements SuggestedQueriesInterface
private $recommendationsFactory;
/**
+ * DataProvider constructor.
+ *
* @param ScopeConfigInterface $scopeConfig
* @param \Magento\Catalog\Model\Layer\Resolver $layerResolver
* @param \Magento\AdvancedSearch\Model\ResourceModel\RecommendationsFactory $recommendationsFactory
@@ -69,18 +74,20 @@ public function __construct(
}
/**
+ * Is Results Count Enabled
+ *
* @return bool
*/
public function isResultsCountEnabled()
{
- return (bool)$this->scopeConfig->getValue(
+ return $this->scopeConfig->isSetFlag(
self::CONFIG_RESULTS_COUNT_ENABLED,
ScopeInterface::SCOPE_STORE
);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getItems(QueryInterface $query)
{
@@ -102,6 +109,8 @@ public function getItems(QueryInterface $query)
}
/**
+ * Return Search Recommendations
+ *
* @param QueryInterface $query
* @return array
*/
@@ -126,17 +135,21 @@ private function getSearchRecommendations(\Magento\Search\Model\QueryInterface $
}
/**
+ * Is Search Recommendations Enabled
+ *
* @return bool
*/
private function isSearchRecommendationsEnabled()
{
- return (bool)$this->scopeConfig->getValue(
+ return $this->scopeConfig->isSetFlag(
self::CONFIG_IS_ENABLED,
ScopeInterface::SCOPE_STORE
);
}
/**
+ * Return Search Recommendations Count
+ *
* @return int
*/
private function getSearchRecommendationsCount()
diff --git a/app/code/Magento/AdvancedSearch/etc/db_schema.xml b/app/code/Magento/AdvancedSearch/etc/db_schema.xml
index 9fae4041109..2dd8c68e2d5 100644
--- a/app/code/Magento/AdvancedSearch/etc/db_schema.xml
+++ b/app/code/Magento/AdvancedSearch/etc/db_schema.xml
@@ -14,13 +14,13 @@
default="0" comment="Query Id"/>
-
+
-
-
diff --git a/app/code/Magento/Analytics/etc/adminhtml/system.xml b/app/code/Magento/Analytics/etc/adminhtml/system.xml
index 4e21648d00c..c7da840b7e6 100644
--- a/app/code/Magento/Analytics/etc/adminhtml/system.xml
+++ b/app/code/Magento/Analytics/etc/adminhtml/system.xml
@@ -36,6 +36,9 @@
Magento\Analytics\Model\Config\Source\Vertical
Magento\Analytics\Model\Config\Backend\Vertical
Magento\Analytics\Block\Adminhtml\System\Config\Vertical
+
+ 1
+
Enabled Template Path Hints for Admin
diff --git a/app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js b/app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js
index 0a692a9b868..c2a0d4dab1f 100644
--- a/app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js
+++ b/app/code/Magento/Backend/view/adminhtml/web/js/validate-store.js
@@ -67,6 +67,7 @@ define([
* 'Confirm' action handler.
*/
confirm: function () {
+ $('body').trigger('processStart');
dataPost().postData(requestData);
}
}
diff --git a/app/code/Magento/Braintree/Controller/Paypal/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php
index ca252aabe54..14ec829d980 100644
--- a/app/code/Magento/Braintree/Controller/Paypal/Review.php
+++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php
@@ -12,11 +12,12 @@
use Magento\Braintree\Gateway\Config\PayPal\Config;
use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\App\Action\HttpPostActionInterface;
/**
* Class Review
*/
-class Review extends AbstractAction
+class Review extends AbstractAction implements HttpPostActionInterface
{
/**
* @var QuoteUpdater
@@ -60,7 +61,7 @@ public function execute()
try {
$this->validateQuote($quote);
- if ($this->validateRequestData($requestData)) {
+ if ($requestData && $this->validateRequestData($requestData)) {
$this->quoteUpdater->execute(
$requestData['nonce'],
$requestData['details'],
@@ -91,6 +92,8 @@ public function execute()
}
/**
+ * Validate request data
+ *
* @param array $requestData
* @return boolean
*/
diff --git a/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php
index a035c84b4ca..4d63ee4125b 100644
--- a/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php
+++ b/app/code/Magento/Braintree/Gateway/Request/PayPal/VaultDataBuilder.php
@@ -49,6 +49,8 @@ public function build(array $buildSubject)
$payment = $paymentDO->getPayment();
$data = $payment->getAdditionalInformation();
+ // the payment token could be stored only if a customer checks the Vault flow on storefront
+ // see https://developers.braintreepayments.com/guides/paypal/vault/javascript/v2#invoking-the-vault-flow
if (!empty($data[VaultConfigProvider::IS_ACTIVE_CODE])) {
$result[self::$optionsKey] = [
self::$storeInVaultOnSuccess => true
diff --git a/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php b/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php
index 4280663178e..950634ba2d9 100644
--- a/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php
+++ b/app/code/Magento/Braintree/Gateway/Request/VaultCaptureDataBuilder.php
@@ -6,6 +6,8 @@
namespace Magento\Braintree\Gateway\Request;
use Magento\Braintree\Gateway\SubjectReader;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Payment\Gateway\Command\CommandException;
use Magento\Payment\Gateway\Request\BuilderInterface;
use Magento\Payment\Helper\Formatter;
@@ -41,6 +43,9 @@ public function build(array $buildSubject)
$payment = $paymentDO->getPayment();
$extensionAttributes = $payment->getExtensionAttributes();
$paymentToken = $extensionAttributes->getVaultPaymentToken();
+ if ($paymentToken === null) {
+ throw new CommandException(__('The Payment Token is not available to perform the request.'));
+ }
return [
'amount' => $this->formatPrice($this->subjectReader->readAmount($buildSubject)),
'paymentMethodToken' => $paymentToken->getGatewayToken()
diff --git a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
index fe589554154..aa23fa767d1 100644
--- a/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
+++ b/app/code/Magento/Braintree/Model/Paypal/Helper/QuoteUpdater.php
@@ -148,7 +148,7 @@ private function updateBillingAddress(Quote $quote, array $details)
{
$billingAddress = $quote->getBillingAddress();
- if ($this->config->isRequiredBillingAddress()) {
+ if ($this->config->isRequiredBillingAddress() && !empty($details['billingAddress'])) {
$this->updateAddressData($billingAddress, $details['billingAddress']);
} else {
$this->updateAddressData($billingAddress, $details['shippingAddress']);
diff --git a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php
index 25ccd8b32d1..d4e1f2745e3 100644
--- a/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Gateway/Request/VaultCaptureDataBuilderTest.php
@@ -3,10 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Braintree\Test\Unit\Gateway\Request;
-use Magento\Braintree\Gateway\SubjectReader;
use Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder;
+use Magento\Braintree\Gateway\SubjectReader;
use Magento\Payment\Gateway\Data\PaymentDataObjectInterface;
use Magento\Sales\Api\Data\OrderPaymentExtension;
use Magento\Sales\Model\Order\Payment;
@@ -26,47 +28,46 @@ class VaultCaptureDataBuilderTest extends \PHPUnit\Framework\TestCase
/**
* @var PaymentDataObjectInterface|MockObject
*/
- private $paymentDOMock;
+ private $paymentDO;
/**
* @var Payment|MockObject
*/
- private $paymentMock;
+ private $payment;
/**
- * @var SubjectReader|\PHPUnit_Framework_MockObject_MockObject
+ * @var SubjectReader|MockObject
*/
- private $subjectReaderMock;
+ private $subjectReader;
/**
* @inheritdoc
*/
- protected function setUp()
+ protected function setUp(): void
{
- $this->paymentDOMock = $this->createMock(PaymentDataObjectInterface::class);
- $this->paymentMock = $this->getMockBuilder(Payment::class)
+ $this->paymentDO = $this->createMock(PaymentDataObjectInterface::class);
+ $this->payment = $this->getMockBuilder(Payment::class)
->disableOriginalConstructor()
->getMock();
- $this->paymentDOMock->expects(static::once())
- ->method('getPayment')
- ->willReturn($this->paymentMock);
+ $this->paymentDO->method('getPayment')
+ ->willReturn($this->payment);
- $this->subjectReaderMock = $this->getMockBuilder(SubjectReader::class)
+ $this->subjectReader = $this->getMockBuilder(SubjectReader::class)
->disableOriginalConstructor()
->getMock();
- $this->builder = new VaultCaptureDataBuilder($this->subjectReaderMock);
+ $this->builder = new VaultCaptureDataBuilder($this->subjectReader);
}
/**
- * \Magento\Braintree\Gateway\Request\VaultCaptureDataBuilder::build
+ * Checks the result after builder execution.
*/
- public function testBuild()
+ public function testBuild(): void
{
$amount = 30.00;
$token = '5tfm4c';
$buildSubject = [
- 'payment' => $this->paymentDOMock,
+ 'payment' => $this->paymentDO,
'amount' => $amount,
];
@@ -75,36 +76,68 @@ public function testBuild()
'paymentMethodToken' => $token,
];
- $this->subjectReaderMock->expects(self::once())
- ->method('readPayment')
+ $this->subjectReader->method('readPayment')
->with($buildSubject)
- ->willReturn($this->paymentDOMock);
- $this->subjectReaderMock->expects(self::once())
- ->method('readAmount')
+ ->willReturn($this->paymentDO);
+ $this->subjectReader->method('readAmount')
->with($buildSubject)
->willReturn($amount);
- $paymentExtensionMock = $this->getMockBuilder(OrderPaymentExtension::class)
+ /** @var OrderPaymentExtension|MockObject $paymentExtension */
+ $paymentExtension = $this->getMockBuilder(OrderPaymentExtension::class)
->setMethods(['getVaultPaymentToken'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $paymentTokenMock = $this->getMockBuilder(PaymentToken::class)
+ /** @var PaymentToken|MockObject $paymentToken */
+ $paymentToken = $this->getMockBuilder(PaymentToken::class)
->disableOriginalConstructor()
->getMock();
- $paymentExtensionMock->expects(static::once())
- ->method('getVaultPaymentToken')
- ->willReturn($paymentTokenMock);
- $this->paymentMock->expects(static::once())
- ->method('getExtensionAttributes')
- ->willReturn($paymentExtensionMock);
+ $paymentExtension->method('getVaultPaymentToken')
+ ->willReturn($paymentToken);
+ $this->payment->method('getExtensionAttributes')
+ ->willReturn($paymentExtension);
- $paymentTokenMock->expects(static::once())
- ->method('getGatewayToken')
+ $paymentToken->method('getGatewayToken')
->willReturn($token);
$result = $this->builder->build($buildSubject);
self::assertEquals($expected, $result);
}
+
+ /**
+ * Checks a builder execution if Payment Token doesn't exist.
+ *
+ * @expectedException \Magento\Payment\Gateway\Command\CommandException
+ * @expectedExceptionMessage The Payment Token is not available to perform the request.
+ */
+ public function testBuildWithoutPaymentToken(): void
+ {
+ $amount = 30.00;
+ $buildSubject = [
+ 'payment' => $this->paymentDO,
+ 'amount' => $amount,
+ ];
+
+ $this->subjectReader->method('readPayment')
+ ->with($buildSubject)
+ ->willReturn($this->paymentDO);
+ $this->subjectReader->method('readAmount')
+ ->with($buildSubject)
+ ->willReturn($amount);
+
+ /** @var OrderPaymentExtension|MockObject $paymentExtension */
+ $paymentExtension = $this->getMockBuilder(OrderPaymentExtension::class)
+ ->setMethods(['getVaultPaymentToken'])
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+
+ $this->payment->method('getExtensionAttributes')
+ ->willReturn($paymentExtension);
+ $paymentExtension->method('getVaultPaymentToken')
+ ->willReturn(null);
+
+ $this->builder->build($buildSubject);
+ }
}
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php
index 62452228b61..a2b5380d288 100644
--- a/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Model/Paypal/Helper/QuoteUpdaterTest.php
@@ -3,23 +3,24 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Braintree\Test\Unit\Model\Paypal\Helper;
-use Magento\Quote\Model\Quote;
-use Magento\Quote\Model\Quote\Address;
-use Magento\Quote\Model\Quote\Payment;
-use Magento\Quote\Api\CartRepositoryInterface;
-use Magento\Braintree\Model\Ui\PayPal\ConfigProvider;
-use Magento\Braintree\Observer\DataAssignObserver;
use Magento\Braintree\Gateway\Config\PayPal\Config;
use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater;
+use Magento\Braintree\Model\Ui\PayPal\ConfigProvider;
+use Magento\Braintree\Observer\DataAssignObserver;
+use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Api\Data\CartExtensionInterface;
+use Magento\Quote\Model\Quote;
+use Magento\Quote\Model\Quote\Address;
+use Magento\Quote\Model\Quote\Payment;
+use PHPUnit_Framework_MockObject_MockObject as MockObject;
/**
* Class QuoteUpdaterTest
*
- * @see \Magento\Braintree\Model\Paypal\Helper\QuoteUpdater
- *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase
@@ -27,24 +28,24 @@ class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase
const TEST_NONCE = '3ede7045-2aea-463e-9754-cd658ffeeb48';
/**
- * @var Config|\PHPUnit_Framework_MockObject_MockObject
+ * @var Config|MockObject
*/
- private $configMock;
+ private $config;
/**
- * @var CartRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var CartRepositoryInterface|MockObject
*/
- private $quoteRepositoryMock;
+ private $quoteRepository;
/**
- * @var Address|\PHPUnit_Framework_MockObject_MockObject
+ * @var Address|MockObject
*/
- private $billingAddressMock;
+ private $billingAddress;
/**
- * @var Address|\PHPUnit_Framework_MockObject_MockObject
+ * @var Address|MockObject
*/
- private $shippingAddressMock;
+ private $shippingAddress;
/**
* @var QuoteUpdater
@@ -52,17 +53,17 @@ class QuoteUpdaterTest extends \PHPUnit\Framework\TestCase
private $quoteUpdater;
/**
- * @return void
+ * @inheritdoc
*/
protected function setUp()
{
- $this->configMock = $this->getMockBuilder(Config::class)
+ $this->config = $this->getMockBuilder(Config::class)
->disableOriginalConstructor()
->getMock();
- $this->quoteRepositoryMock = $this->getMockBuilder(CartRepositoryInterface::class)
+ $this->quoteRepository = $this->getMockBuilder(CartRepositoryInterface::class)
->getMockForAbstractClass();
- $this->billingAddressMock = $this->getMockBuilder(Address::class)
+ $this->billingAddress = $this->getMockBuilder(Address::class)
->setMethods(
[
'setLastname',
@@ -77,9 +78,10 @@ protected function setUp()
'setShouldIgnoreValidation',
'getEmail'
]
- )->disableOriginalConstructor()
+ )
+ ->disableOriginalConstructor()
->getMock();
- $this->shippingAddressMock = $this->getMockBuilder(Address::class)
+ $this->shippingAddress = $this->getMockBuilder(Address::class)
->setMethods(
[
'setLastname',
@@ -93,61 +95,61 @@ protected function setUp()
'setPostcode',
'setShouldIgnoreValidation'
]
- )->disableOriginalConstructor()
+ )
+ ->disableOriginalConstructor()
->getMock();
$this->quoteUpdater = new QuoteUpdater(
- $this->configMock,
- $this->quoteRepositoryMock
+ $this->config,
+ $this->quoteRepository
);
}
/**
- * @return void
+ * Checks if quote details can be update by the response from Braintree.
+ *
* @throws \Magento\Framework\Exception\LocalizedException
*/
- public function testExecute()
+ public function testExecute(): void
{
$details = $this->getDetails();
- $quoteMock = $this->getQuoteMock();
- $paymentMock = $this->getPaymentMock();
+ $quote = $this->getQuoteMock();
+ $payment = $this->getPaymentMock();
- $quoteMock->expects(self::once())
- ->method('getPayment')
- ->willReturn($paymentMock);
+ $quote->method('getPayment')
+ ->willReturn($payment);
- $paymentMock->expects(self::once())
- ->method('setMethod')
+ $payment->method('setMethod')
->with(ConfigProvider::PAYPAL_CODE);
- $paymentMock->expects(self::once())
- ->method('setAdditionalInformation')
+ $payment->method('setAdditionalInformation')
->with(DataAssignObserver::PAYMENT_METHOD_NONCE, self::TEST_NONCE);
- $this->updateQuoteStep($quoteMock, $details);
+ $this->updateQuoteStep($quote, $details);
- $this->quoteUpdater->execute(self::TEST_NONCE, $details, $quoteMock);
+ $this->quoteUpdater->execute(self::TEST_NONCE, $details, $quote);
}
/**
+ * Disables quote's addresses validation.
+ *
* @return void
*/
- private function disabledQuoteAddressValidationStep()
+ private function disabledQuoteAddressValidationStep(): void
{
- $this->billingAddressMock->expects(self::once())
- ->method('setShouldIgnoreValidation')
+ $this->billingAddress->method('setShouldIgnoreValidation')
->with(true);
- $this->shippingAddressMock->expects(self::once())
- ->method('setShouldIgnoreValidation')
+ $this->shippingAddress->method('setShouldIgnoreValidation')
->with(true);
- $this->billingAddressMock->expects(self::once())
- ->method('getEmail')
+ $this->billingAddress->method('getEmail')
->willReturn('bt_buyer_us@paypal.com');
}
/**
+ * Gets quote's details.
+ *
* @return array
*/
- private function getDetails()
+ private function getDetails(): array
{
return [
'email' => 'bt_buyer_us@paypal.com',
@@ -177,54 +179,51 @@ private function getDetails()
}
/**
+ * Updates shipping address details.
+ *
* @param array $details
*/
- private function updateShippingAddressStep(array $details)
+ private function updateShippingAddressStep(array $details): void
{
- $this->shippingAddressMock->expects(self::once())
- ->method('setLastname')
+ $this->shippingAddress->method('setLastname')
->with($details['lastName']);
- $this->shippingAddressMock->expects(self::once())
- ->method('setFirstname')
+ $this->shippingAddress->method('setFirstname')
->with($details['firstName']);
- $this->shippingAddressMock->expects(self::once())
- ->method('setEmail')
+ $this->shippingAddress->method('setEmail')
->with($details['email']);
- $this->shippingAddressMock->expects(self::once())
- ->method('setCollectShippingRates')
+ $this->shippingAddress->method('setCollectShippingRates')
->with(true);
- $this->updateAddressDataStep($this->shippingAddressMock, $details['shippingAddress']);
+ $this->updateAddressDataStep($this->shippingAddress, $details['shippingAddress']);
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $addressMock
+ * Updates address details.
+ *
+ * @param MockObject $address
* @param array $addressData
*/
- private function updateAddressDataStep(\PHPUnit_Framework_MockObject_MockObject $addressMock, array $addressData)
+ private function updateAddressDataStep(MockObject $address, array $addressData): void
{
- $addressMock->expects(self::once())
- ->method('setStreet')
+ $address->method('setStreet')
->with([$addressData['streetAddress'], $addressData['extendedAddress']]);
- $addressMock->expects(self::once())
- ->method('setCity')
+ $address->method('setCity')
->with($addressData['locality']);
- $addressMock->expects(self::once())
- ->method('setRegionCode')
+ $address->method('setRegionCode')
->with($addressData['region']);
- $addressMock->expects(self::once())
- ->method('setCountryId')
+ $address->method('setCountryId')
->with($addressData['countryCodeAlpha2']);
- $addressMock->expects(self::once())
- ->method('setPostcode')
+ $address->method('setPostcode')
->with($addressData['postalCode']);
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Updates quote's address details.
+ *
+ * @param MockObject $quoteMock
* @param array $details
*/
- private function updateQuoteAddressStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock, array $details)
+ private function updateQuoteAddressStep(MockObject $quoteMock, array $details): void
{
$quoteMock->expects(self::exactly(2))
->method('getIsVirtual')
@@ -235,64 +234,61 @@ private function updateQuoteAddressStep(\PHPUnit_Framework_MockObject_MockObject
}
/**
+ * Updates billing address details.
+ *
* @param array $details
*/
- private function updateBillingAddressStep(array $details)
+ private function updateBillingAddressStep(array $details): void
{
- $this->configMock->expects(self::once())
- ->method('isRequiredBillingAddress')
+ $this->config->method('isRequiredBillingAddress')
->willReturn(true);
- $this->updateAddressDataStep($this->billingAddressMock, $details['billingAddress']);
+ $this->updateAddressDataStep($this->billingAddress, $details['billingAddress']);
- $this->billingAddressMock->expects(self::once())
- ->method('setLastname')
+ $this->billingAddress->method('setLastname')
->with($details['lastName']);
- $this->billingAddressMock->expects(self::once())
- ->method('setFirstname')
+ $this->billingAddress->method('setFirstname')
->with($details['firstName']);
- $this->billingAddressMock->expects(self::once())
- ->method('setEmail')
+ $this->billingAddress->method('setEmail')
->with($details['email']);
}
/**
- * @param \PHPUnit_Framework_MockObject_MockObject $quoteMock
+ * Updates quote details.
+ *
+ * @param MockObject $quote
* @param array $details
*/
- private function updateQuoteStep(\PHPUnit_Framework_MockObject_MockObject $quoteMock, array $details)
+ private function updateQuoteStep(MockObject $quote, array $details): void
{
- $quoteMock->expects(self::once())
- ->method('setMayEditShippingAddress')
+ $quote->method('setMayEditShippingAddress')
->with(false);
- $quoteMock->expects(self::once())
- ->method('setMayEditShippingMethod')
+ $quote->method('setMayEditShippingMethod')
->with(true);
- $quoteMock->expects(self::exactly(2))
- ->method('getShippingAddress')
- ->willReturn($this->shippingAddressMock);
- $quoteMock->expects(self::exactly(2))
+ $quote->method('getShippingAddress')
+ ->willReturn($this->shippingAddress);
+ $quote->expects(self::exactly(2))
->method('getBillingAddress')
- ->willReturn($this->billingAddressMock);
+ ->willReturn($this->billingAddress);
- $this->updateQuoteAddressStep($quoteMock, $details);
+ $this->updateQuoteAddressStep($quote, $details);
$this->disabledQuoteAddressValidationStep();
- $quoteMock->expects(self::once())
- ->method('collectTotals');
+ $quote->method('collectTotals');
- $this->quoteRepositoryMock->expects(self::once())
- ->method('save')
- ->with($quoteMock);
+ $this->quoteRepository->method('save')
+ ->with($quote);
}
/**
- * @return Quote|\PHPUnit_Framework_MockObject_MockObject
+ * Creates a mock for Quote object.
+ *
+ * @return Quote|MockObject
*/
- private function getQuoteMock()
+ private function getQuoteMock(): MockObject
{
- $quoteMock = $this->getMockBuilder(Quote::class)
+ $quote = $this->getMockBuilder(Quote::class)
->setMethods(
[
'getIsVirtual',
@@ -304,25 +300,27 @@ private function getQuoteMock()
'getBillingAddress',
'getExtensionAttributes'
]
- )->disableOriginalConstructor()
+ )
+ ->disableOriginalConstructor()
->getMock();
- $cartExtensionMock = $this->getMockBuilder(CartExtensionInterface::class)
+ $cartExtension = $this->getMockBuilder(CartExtensionInterface::class)
->setMethods(['setShippingAssignments'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $quoteMock->expects(self::any())
- ->method('getExtensionAttributes')
- ->willReturn($cartExtensionMock);
+ $quote->method('getExtensionAttributes')
+ ->willReturn($cartExtension);
- return $quoteMock;
+ return $quote;
}
/**
- * @return Payment|\PHPUnit_Framework_MockObject_MockObject
+ * Creates a mock for Payment object.
+ *
+ * @return Payment|MockObject
*/
- private function getPaymentMock()
+ private function getPaymentMock(): MockObject
{
return $this->getMockBuilder(Payment::class)
->disableOriginalConstructor()
diff --git a/app/code/Magento/Braintree/i18n/en_US.csv b/app/code/Magento/Braintree/i18n/en_US.csv
index 6bf677151ed..7bd305f546d 100644
--- a/app/code/Magento/Braintree/i18n/en_US.csv
+++ b/app/code/Magento/Braintree/i18n/en_US.csv
@@ -192,3 +192,4 @@ Currency,Currency
"Too many concurrent attempts to refund this transaction. Try again later.","Too many concurrent attempts to refund this transaction. Try again later."
"Too many concurrent attempts to void this transaction. Try again later.","Too many concurrent attempts to void this transaction. Try again later."
"Braintree Settlement","Braintree Settlement"
+"The Payment Token is not available to perform the request.","The Payment Token is not available to perform the request."
diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php
index 62a2fa1c47e..fa488b073f5 100644
--- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php
+++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle.php
@@ -7,6 +7,7 @@
use Magento\Bundle\Model\Option;
use Magento\Catalog\Model\Product;
+use Magento\Framework\DataObject;
/**
* Catalog bundle product info block
@@ -170,7 +171,7 @@ public function getJsonConfig()
$defaultValues = [];
$preConfiguredFlag = $currentProduct->hasPreconfiguredValues();
- /** @var \Magento\Framework\DataObject|null $preConfiguredValues */
+ /** @var DataObject|null $preConfiguredValues */
$preConfiguredValues = $preConfiguredFlag ? $currentProduct->getPreconfiguredValues() : null;
$position = 0;
@@ -193,12 +194,13 @@ public function getJsonConfig()
$options[$optionId]['selections'][$configValue]['qty'] = $configQty;
}
}
+ $options = $this->processOptions($optionId, $options, $preConfiguredValues);
}
$position++;
}
$config = $this->getConfigData($currentProduct, $options);
- $configObj = new \Magento\Framework\DataObject(
+ $configObj = new DataObject(
[
'config' => $config,
]
@@ -403,4 +405,30 @@ private function getConfigData(Product $product, array $options)
];
return $config;
}
+
+ /**
+ * Set preconfigured quantities and selections to options.
+ *
+ * @param string $optionId
+ * @param array $options
+ * @param DataObject $preConfiguredValues
+ * @return array
+ */
+ private function processOptions(string $optionId, array $options, DataObject $preConfiguredValues)
+ {
+ $preConfiguredQtys = $preConfiguredValues->getData("bundle_option_qty/${optionId}") ?? [];
+ $selections = $options[$optionId]['selections'];
+ array_walk($selections, function (&$selection, $selectionId) use ($preConfiguredQtys) {
+ if (is_array($preConfiguredQtys) && isset($preConfiguredQtys[$selectionId])) {
+ $selection['qty'] = $preConfiguredQtys[$selectionId];
+ } else {
+ if ((int)$preConfiguredQtys > 0) {
+ $selection['qty'] = $preConfiguredQtys;
+ }
+ }
+ });
+ $options[$optionId]['selections'] = $selections;
+
+ return $options;
+ }
}
diff --git a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php
index 5d326e7c01d..7c63af0bd0e 100644
--- a/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php
+++ b/app/code/Magento/Bundle/Block/Catalog/Product/View/Type/Bundle/Option.php
@@ -167,7 +167,9 @@ protected function _getSelectedOptions()
*/
protected function assignSelection(\Magento\Bundle\Model\Option $option, $selectionId)
{
- if ($selectionId && $option->getSelectionById($selectionId)) {
+ if (is_array($selectionId)) {
+ $this->_selectedOptions = $selectionId;
+ } else if ($selectionId && $option->getSelectionById($selectionId)) {
$this->_selectedOptions = $selectionId;
} elseif (!$option->getRequired()) {
$this->_selectedOptions = 'None';
@@ -228,6 +230,8 @@ public function getProduct()
}
/**
+ * Get bundle option price title.
+ *
* @param \Magento\Catalog\Model\Product $selection
* @param bool $includeContainer
* @return string
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml
index f94cd83f4e7..58806126aee 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontEditBundleProductTest.xml
@@ -94,9 +94,8 @@
-
-
-
+
+
diff --git a/app/code/Magento/Bundle/etc/db_schema.xml b/app/code/Magento/Bundle/etc/db_schema.xml
index f1c1e7ae782..33738cd252d 100644
--- a/app/code/Magento/Bundle/etc/db_schema.xml
+++ b/app/code/Magento/Bundle/etc/db_schema.xml
@@ -18,13 +18,13 @@
-
+
-
-
+
@@ -39,13 +39,13 @@
-
+
-
-
+
@@ -73,19 +73,19 @@
comment="Selection Qty"/>
-
+
-
-
-
+
-
+
@@ -101,19 +101,19 @@
nullable="false" default="0" comment="Selection Price Value"/>
-
+
-
-
-
+
@@ -129,24 +129,24 @@
comment="Min Price"/>
-
+
-
-
-
-
+
-
+
@@ -162,7 +162,7 @@
default="0" comment="Option Id"/>
-
+
@@ -197,7 +197,7 @@
comment="Tier Price"/>
-
+
@@ -231,7 +231,7 @@
comment="Tier Price"/>
-
+
@@ -257,7 +257,7 @@
comment="Price"/>
-
+
@@ -285,7 +285,7 @@
comment="Price"/>
-
+
@@ -313,7 +313,7 @@
comment="Tier Price"/>
-
+
@@ -340,7 +340,7 @@
comment="Tier Price"/>
-
+
diff --git a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
index 8acf170d43c..727e18280d7 100644
--- a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
+++ b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
@@ -7,6 +7,9 @@
use Magento\Framework\Cache\InvalidateLogger;
+/**
+ * Class PurgeCache
+ */
class PurgeCache
{
const HEADER_X_MAGENTO_TAGS_PATTERN = 'X-Magento-Tags-Pattern';
@@ -26,6 +29,18 @@ class PurgeCache
*/
private $logger;
+ /**
+ * Batch size of the purge request.
+ *
+ * Based on default Varnish 4 http_req_hdr_len size minus a 512 bytes margin for method,
+ * header name, line feeds etc.
+ *
+ * @see https://varnish-cache.org/docs/4.1/reference/varnishd.html
+ *
+ * @var int
+ */
+ private $requestSize = 7680;
+
/**
* Constructor
*
@@ -44,18 +59,65 @@ public function __construct(
}
/**
- * Send curl purge request
- * to invalidate cache by tags pattern
+ * Send curl purge request to invalidate cache by tags pattern
*
* @param string $tagsPattern
* @return bool Return true if successful; otherwise return false
*/
public function sendPurgeRequest($tagsPattern)
{
+ $successful = true;
$socketAdapter = $this->socketAdapterFactory->create();
$servers = $this->cacheServer->getUris();
- $headers = [self::HEADER_X_MAGENTO_TAGS_PATTERN => $tagsPattern];
$socketAdapter->setOptions(['timeout' => 10]);
+
+ $formattedTagsChunks = $this->splitTags($tagsPattern);
+ foreach ($formattedTagsChunks as $formattedTagsChunk) {
+ if (!$this->sendPurgeRequestToServers($socketAdapter, $servers, $formattedTagsChunk)) {
+ $successful = false;
+ }
+ }
+
+ return $successful;
+ }
+
+ /**
+ * Split tags by batches
+ *
+ * @param string $tagsPattern
+ * @return \Generator
+ */
+ private function splitTags($tagsPattern)
+ {
+ $tagsBatchSize = 0;
+ $formattedTagsChunk = [];
+ $formattedTags = explode('|', $tagsPattern);
+ foreach ($formattedTags as $formattedTag) {
+ if ($tagsBatchSize + strlen($formattedTag) > $this->requestSize - count($formattedTagsChunk) - 1) {
+ yield implode('|', array_unique($formattedTagsChunk));
+ $formattedTagsChunk = [];
+ $tagsBatchSize = 0;
+ }
+
+ $tagsBatchSize += strlen($formattedTag);
+ $formattedTagsChunk[] = $formattedTag;
+ }
+ if (!empty($formattedTagsChunk)) {
+ yield implode('|', array_unique($formattedTagsChunk));
+ }
+ }
+
+ /**
+ * Send curl purge request to servers to invalidate cache by tags pattern
+ *
+ * @param \Zend\Http\Client\Adapter\Socket $socketAdapter
+ * @param \Zend\Uri\Uri[] $servers
+ * @param string $formattedTagsChunk
+ * @return bool Return true if successful; otherwise return false
+ */
+ private function sendPurgeRequestToServers($socketAdapter, $servers, $formattedTagsChunk)
+ {
+ $headers = [self::HEADER_X_MAGENTO_TAGS_PATTERN => $formattedTagsChunk];
foreach ($servers as $server) {
$headers['Host'] = $server->getHost();
try {
@@ -69,12 +131,11 @@ public function sendPurgeRequest($tagsPattern)
$socketAdapter->read();
$socketAdapter->close();
} catch (\Exception $e) {
- $this->logger->critical($e->getMessage(), compact('server', 'tagsPattern'));
+ $this->logger->critical($e->getMessage(), compact('server', 'formattedTagsChunk'));
return false;
}
}
-
- $this->logger->execute(compact('servers', 'tagsPattern'));
+ $this->logger->execute(compact('servers', 'formattedTagsChunk'));
return true;
}
}
diff --git a/app/code/Magento/Captcha/etc/db_schema.xml b/app/code/Magento/Captcha/etc/db_schema.xml
index fa9a14abb89..b8987363569 100644
--- a/app/code/Magento/Captcha/etc/db_schema.xml
+++ b/app/code/Magento/Captcha/etc/db_schema.xml
@@ -13,7 +13,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php
index ee92fd7c19b..26ffc6e0df3 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Formgroup.php
@@ -11,6 +11,9 @@
use Magento\Backend\Block\Widget\Form;
+/**
+ * Form group for attribute set
+ */
class Formgroup extends \Magento\Backend\Block\Widget\Form\Generic
{
/**
@@ -37,6 +40,8 @@ public function __construct(
}
/**
+ * Prepare form elements
+ *
* @return void
*/
protected function _prepareForm()
@@ -77,13 +82,15 @@ protected function _prepareForm()
}
/**
+ * Returns set id
+ *
* @return int
*/
protected function _getSetId()
{
- return intval(
+ return (int)(
$this->getRequest()->getParam('id')
- ) > 0 ? intval(
+ ) > 0 ? (int)(
$this->getRequest()->getParam('id')
) : $this->_typeFactory->create()->load(
$this->_coreRegistry->registry('entityType')
diff --git a/app/code/Magento/Catalog/Block/Product/Image.php b/app/code/Magento/Catalog/Block/Product/Image.php
index 20a556ab414..7a7f9c0affc 100644
--- a/app/code/Magento/Catalog/Block/Product/Image.php
+++ b/app/code/Magento/Catalog/Block/Product/Image.php
@@ -6,6 +6,8 @@
namespace Magento\Catalog\Block\Product;
/**
+ * Product image block
+ *
* @api
* @method string getImageUrl()
* @method string getWidth()
@@ -13,6 +15,7 @@
* @method string getLabel()
* @method float getRatio()
* @method string getCustomAttributes()
+ * @method string getClass()
* @since 100.0.2
*/
class Image extends \Magento\Framework\View\Element\Template
diff --git a/app/code/Magento/Catalog/Block/Product/ImageFactory.php b/app/code/Magento/Catalog/Block/Product/ImageFactory.php
index f9a576367dd..aa303af656a 100644
--- a/app/code/Magento/Catalog/Block/Product/ImageFactory.php
+++ b/app/code/Magento/Catalog/Block/Product/ImageFactory.php
@@ -77,16 +77,29 @@ private function getStringCustomAttributes(array $attributes): string
{
$result = [];
foreach ($attributes as $name => $value) {
- $result[] = $name . '="' . $value . '"';
+ if ($name != 'class') {
+ $result[] = $name . '="' . $value . '"';
+ }
}
return !empty($result) ? implode(' ', $result) : '';
}
+ /**
+ * Retrieve image class for HTML element
+ *
+ * @param array $attributes
+ * @return string
+ */
+ private function getClass(array $attributes): string
+ {
+ return $attributes['class'] ?? 'product-image-photo';
+ }
+
/**
* Calculate image ratio
*
- * @param $width
- * @param $height
+ * @param int $width
+ * @param int $height
* @return float
*/
private function getRatio(int $width, int $height): float
@@ -98,8 +111,9 @@ private function getRatio(int $width, int $height): float
}
/**
- * @param Product $product
+ * Get image label
*
+ * @param Product $product
* @param string $imageType
* @return string
*/
@@ -114,6 +128,7 @@ private function getLabel(Product $product, string $imageType): string
/**
* Create image block from product
+ *
* @param Product $product
* @param string $imageId
* @param array|null $attributes
@@ -154,6 +169,7 @@ public function create(Product $product, string $imageId, array $attributes = nu
'label' => $this->getLabel($product, $imageMiscParams['image_type']),
'ratio' => $this->getRatio($imageMiscParams['image_width'], $imageMiscParams['image_height']),
'custom_attributes' => $this->getStringCustomAttributes($attributes),
+ 'class' => $this->getClass($attributes),
'product_id' => $product->getId()
],
];
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
index 985d812fc74..49e601357c6 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
@@ -259,14 +259,14 @@ public function execute()
$data['backend_model'] = $this->productHelper->getAttributeBackendModelByInputType(
$data['frontend_input']
);
+
+ if ($model->getIsUserDefined() === null) {
+ $data['backend_type'] = $model->getBackendTypeByInput($data['frontend_input']);
+ }
}
$data += ['is_filterable' => 0, 'is_filterable_in_search' => 0];
- if ($model->getIsUserDefined() === null || $model->getIsUserDefined() != 0) {
- $data['backend_type'] = $model->getBackendTypeByInput($data['frontend_input']);
- }
-
$defaultValueField = $model->getDefaultValueByInput($data['frontend_input']);
if ($defaultValueField) {
$data['default_value'] = $this->getRequest()->getParam($defaultValueField);
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
index d82f4a04fb2..f11d16755ef 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
@@ -19,6 +19,8 @@
use Magento\Catalog\Controller\Adminhtml\Product\Initialization\Helper\AttributeFilter;
/**
+ * Product helper
+ *
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
@@ -365,6 +367,8 @@ private function overwriteValue($optionId, $option, $overwriteOptions)
}
/**
+ * Get link resolver instance
+ *
* @return LinkResolver
* @deprecated 101.0.0
*/
@@ -377,6 +381,8 @@ private function getLinkResolver()
}
/**
+ * Get DateTimeFilter instance
+ *
* @return \Magento\Framework\Stdlib\DateTime\Filter\DateTime
* @deprecated 101.0.0
*/
@@ -391,6 +397,7 @@ private function getDateTimeFilter()
/**
* Remove ids of non selected websites from $websiteIds array and return filtered data
+ *
* $websiteIds parameter expects array with website ids as keys and 1 (selected) or 0 (non selected) as values
* Only one id (default website ID) will be set to $websiteIds array when the single store mode is turned on
*
@@ -463,6 +470,7 @@ private function fillProductOptions(Product $product, array $productOptions)
private function convertSpecialFromDateStringToObject($productData)
{
if (isset($productData['special_from_date']) && $productData['special_from_date'] != '') {
+ $productData['special_from_date'] = $this->getDateTimeFilter()->filter($productData['special_from_date']);
$productData['special_from_date'] = new \DateTime($productData['special_from_date']);
}
diff --git a/app/code/Magento/Catalog/Model/AbstractModel.php b/app/code/Magento/Catalog/Model/AbstractModel.php
index 007635b1243..78a49cd1e8b 100644
--- a/app/code/Magento/Catalog/Model/AbstractModel.php
+++ b/app/code/Magento/Catalog/Model/AbstractModel.php
@@ -179,7 +179,7 @@ public function isLockedAttribute($attributeCode)
*
* @param string|array $key
* @param mixed $value
- * @return \Magento\Framework\DataObject
+ * @return $this
*/
public function setData($key, $value = null)
{
@@ -282,9 +282,9 @@ public function getWebsiteStoreIds()
*
* Default value existing is flag for using store value in data
*
- * @param string $attributeCode
- * @param mixed $value
- * @return $this
+ * @param string $attributeCode
+ * @param mixed $value
+ * @return $this
*
* @deprecated 101.0.0
*/
@@ -332,11 +332,10 @@ public function getAttributeDefaultValue($attributeCode)
}
/**
- * Set attribute code flag if attribute has value in current store and does not use
- * value of default store as value
+ * Set attribute code flag if attribute has value in current store and does not use value of default store as value
*
- * @param string $attributeCode
- * @return $this
+ * @param string $attributeCode
+ * @return $this
*
* @deprecated 101.0.0
*/
diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php
index f70bab73d08..66a9132ae44 100644
--- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php
+++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ProductCategoryCondition.php
@@ -38,6 +38,7 @@ class ProductCategoryCondition implements CustomConditionInterface
/**
* @param \Magento\Framework\App\ResourceConnection $resourceConnection
+ * @param \Magento\Catalog\Model\CategoryRepository $categoryRepository
*/
public function __construct(
\Magento\Framework\App\ResourceConnection $resourceConnection,
@@ -104,7 +105,7 @@ private function getCategoryIds(Filter $filter): array
}
}
- return array_unique(array_merge($categoryIds, ...$childCategoryIds));
+ return array_map('intval', array_unique(array_merge($categoryIds, ...$childCategoryIds)));
}
/**
diff --git a/app/code/Magento/Catalog/Model/FilterProductCustomAttribute.php b/app/code/Magento/Catalog/Model/FilterProductCustomAttribute.php
index b83bb97301b..497ed2fd499 100644
--- a/app/code/Magento/Catalog/Model/FilterProductCustomAttribute.php
+++ b/app/code/Magento/Catalog/Model/FilterProductCustomAttribute.php
@@ -27,11 +27,12 @@ public function __construct(array $blackList = [])
/**
* Delete custom attribute
- * @param array $attributes
+ *
+ * @param array $attributes set objects attributes @example ['attribute_code'=>'attribute_object']
* @return array
*/
public function execute(array $attributes): array
{
- return array_diff($attributes, $this->blackList);
+ return array_diff_key($attributes, array_flip($this->blackList));
}
}
diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php
index ce92a2c1d95..b5ca0895d6d 100644
--- a/app/code/Magento/Catalog/Model/ImageUploader.php
+++ b/app/code/Magento/Catalog/Model/ImageUploader.php
@@ -67,14 +67,9 @@ class ImageUploader
/**
* List of allowed image mime types
*
- * @var array
+ * @var string[]
*/
- private $allowedMimeTypes = [
- 'image/jpg',
- 'image/jpeg',
- 'image/gif',
- 'image/png',
- ];
+ private $allowedMimeTypes;
/**
* ImageUploader constructor
@@ -87,6 +82,7 @@ class ImageUploader
* @param string $baseTmpPath
* @param string $basePath
* @param string[] $allowedExtensions
+ * @param string[] $allowedMimeTypes
*/
public function __construct(
\Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase,
@@ -96,7 +92,8 @@ public function __construct(
\Psr\Log\LoggerInterface $logger,
$baseTmpPath,
$basePath,
- $allowedExtensions
+ $allowedExtensions,
+ $allowedMimeTypes = []
) {
$this->coreFileStorageDatabase = $coreFileStorageDatabase;
$this->mediaDirectory = $filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
@@ -106,6 +103,7 @@ public function __construct(
$this->baseTmpPath = $baseTmpPath;
$this->basePath = $basePath;
$this->allowedExtensions = $allowedExtensions;
+ $this->allowedMimeTypes = $allowedMimeTypes;
}
/**
@@ -165,7 +163,7 @@ public function getBasePath()
}
/**
- * Retrieve base path
+ * Retrieve allowed extensions
*
* @return string[]
*/
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php
index 64a8f930d83..a62e3d8f83b 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Full.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Catalog\Model\Indexer\Category\Flat\Action;
+/**
+ * Class for full reindex flat categories
+ */
class Full extends \Magento\Catalog\Model\Indexer\Category\Flat\AbstractAction
{
/**
@@ -92,6 +95,7 @@ protected function populateFlatTables(array $stores)
/**
* Create table and add attributes as fields for specified store.
+ *
* This routine assumes that DDL operations are allowed
*
* @param int $store
@@ -109,6 +113,7 @@ protected function createTable($store)
/**
* Create category flat tables and add attributes as fields.
+ *
* Tables are created only if DDL operations are allowed
*
* @param \Magento\Store\Model\Store[] $stores if empty, create tables for all stores of the application
@@ -167,6 +172,44 @@ protected function switchTables(array $stores = [])
return $this;
}
+ /**
+ * Retrieve all actual Catalog Product Flat Table names
+ *
+ * @return string[]
+ */
+ private function getActualStoreTablesForCategoryFlat(): array
+ {
+ $actualStoreTables = [];
+ foreach ($this->storeManager->getStores() as $store) {
+ $actualStoreTables[] = sprintf(
+ '%s_store_%s',
+ $this->connection->getTableName('catalog_category_flat'),
+ $store->getId()
+ );
+ }
+
+ return $actualStoreTables;
+ }
+
+ /**
+ * Delete all category flat tables for not existing stores
+ *
+ * @return void
+ */
+ private function deleteAbandonedStoreCategoryFlatTables(): void
+ {
+ $existentTables = $this->connection->getTables(
+ $this->connection->getTableName('catalog_category_flat_store_%')
+ );
+ $actualStoreTables = $this->getActualStoreTablesForCategoryFlat();
+
+ $tablesToDelete = array_diff($existentTables, $actualStoreTables);
+
+ foreach ($tablesToDelete as $table) {
+ $this->connection->dropTable($table);
+ }
+ }
+
/**
* Transactional rebuild flat data from eav
*
@@ -182,7 +225,7 @@ public function reindexAll()
$stores = $this->storeManager->getStores();
$this->populateFlatTables($stores);
$this->switchTables($stores);
-
+ $this->deleteAbandonedStoreCategoryFlatTables();
$this->allowTableChanges = true;
return $this;
diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php
index 753fb0b5e18..44c5c891f4d 100644
--- a/app/code/Magento/Catalog/Model/Product.php
+++ b/app/code/Magento/Catalog/Model/Product.php
@@ -71,6 +71,11 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements
*/
const STORE_ID = 'store_id';
+ /**
+ * Product Url path.
+ */
+ const URL_PATH = 'url_path';
+
/**
* @var string
*/
@@ -507,13 +512,17 @@ protected function _getResource()
protected function getCustomAttributesCodes()
{
if ($this->customAttributesCodes === null) {
- $this->customAttributesCodes = array_keys($this->eavConfig->getEntityAttributes(
- self::ENTITY,
- $this
- ));
-
- $this->customAttributesCodes = $this->filterCustomAttribute->execute($this->customAttributesCodes);
- $this->customAttributesCodes = array_diff($this->customAttributesCodes, ProductInterface::ATTRIBUTES);
+ $this->customAttributesCodes = array_diff(
+ array_keys(
+ $this->filterCustomAttribute->execute(
+ $this->eavConfig->getEntityAttributes(
+ self::ENTITY,
+ $this
+ )
+ )
+ ),
+ ProductInterface::ATTRIBUTES
+ );
}
return $this->customAttributesCodes;
@@ -813,6 +822,9 @@ public function getStoreIds()
if (!$this->hasStoreIds()) {
$storeIds = [];
if ($websiteIds = $this->getWebsiteIds()) {
+ if ($this->_storeManager->isSingleStoreMode()) {
+ $websiteIds = array_keys($websiteIds);
+ }
foreach ($websiteIds as $websiteId) {
$websiteStores = $this->_storeManager->getWebsite($websiteId)->getStoreIds();
$storeIds = array_merge($storeIds, $websiteStores);
@@ -923,8 +935,8 @@ public function beforeSave()
*
* If value specified, it will be set.
*
- * @param bool $value
- * @return bool
+ * @param bool $value
+ * @return bool
*/
public function canAffectOptions($value = null)
{
@@ -1045,6 +1057,7 @@ public function reindex()
* Register indexing event before delete product
*
* @return \Magento\Catalog\Model\Product
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function beforeDelete()
{
@@ -1557,6 +1570,7 @@ public function hasGalleryAttribute()
* @param bool $move if true, it will move source file
* @param bool $exclude mark image as disabled in product page view
* @return \Magento\Catalog\Model\Product
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function addImageToMediaGallery($file, $mediaAttribute = null, $move = false, $exclude = true)
{
@@ -1718,8 +1732,6 @@ public function getIsSalable()
/**
* Check is a virtual product
*
- * Data helper wrapper
- *
* @return bool
*/
public function isVirtual()
@@ -1810,7 +1822,7 @@ public function formatUrlKey($str)
/**
* Save current attribute with code $code and assign new value
*
- * @param string $code Attribute code
+ * @param string $code Attribute code
* @param mixed $value New attribute value
* @param int $store Store ID
* @return void
@@ -2028,7 +2040,7 @@ public function getIsVirtual()
*
* @param string $code Option code
* @param mixed $value Value of the option
- * @param int|Product $product Product ID
+ * @param int|Product|null $product Product ID
* @return $this
*/
public function addCustomOption($code, $value, $product = null)
@@ -2560,7 +2572,7 @@ public function setTypeId($typeId)
/**
* @inheritdoc
*
- * @return \Magento\Catalog\Api\Data\ProductExtensionInterface
+ * @return \Magento\Framework\Api\ExtensionAttributesInterface
*/
public function getExtensionAttributes()
{
@@ -2581,10 +2593,11 @@ public function setExtensionAttributes(\Magento\Catalog\Api\Data\ProductExtensio
//@codeCoverageIgnoreEnd
/**
- * Convert array to media gallery interface
+ * Convert Image to ProductAttributeMediaGalleryEntryInterface
*
* @param array $mediaGallery
* @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface[]
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function convertToMediaGalleryInterface(array $mediaGallery)
{
@@ -2600,9 +2613,10 @@ protected function convertToMediaGalleryInterface(array $mediaGallery)
}
/**
- * Returns media gallery entries
+ * Get media gallery entries
*
* @return \Magento\Catalog\Api\Data\ProductAttributeMediaGalleryEntryInterface[]|null
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getMediaGalleryEntries()
{
@@ -2620,6 +2634,7 @@ public function getMediaGalleryEntries()
*
* @param ProductAttributeMediaGalleryEntryInterface[] $mediaGalleryEntries
* @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function setMediaGalleryEntries(array $mediaGalleryEntries = null)
{
@@ -2660,7 +2675,7 @@ public function setId($value)
}
/**
- * Returns link repository instance
+ * Get link repository
*
* @return ProductLinkRepositoryInterface
*/
@@ -2674,7 +2689,7 @@ private function getLinkRepository()
}
/**
- * Returns media gallery processor instance
+ * Get media gallery processor
*
* @return Product\Gallery\Processor
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php
index 44404710eb2..e12f3f7d949 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Sku.php
@@ -107,6 +107,7 @@ public function beforeSave($object)
if ($object->getIsDuplicate()) {
$this->_generateUniqueSku($object);
}
+ $this->trimValue($object);
return parent::beforeSave($object);
}
@@ -137,4 +138,19 @@ protected function _getLastSimilarAttributeValueIncrement($attribute, $object)
$data = $connection->fetchOne($select, $bind);
return abs((int)str_replace($value, '', $data));
}
+
+ /**
+ * Remove extra spaces from attribute value before save.
+ *
+ * @param Product $object
+ * @return void
+ */
+ private function trimValue($object)
+ {
+ $attrCode = $this->getAttribute()->getAttributeCode();
+ $value = $object->getData($attrCode);
+ if ($value) {
+ $object->setData($attrCode, trim($value));
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php
index a112da79d16..b4d6dc2c19e 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/UpdateHandler.php
@@ -92,7 +92,12 @@ public function execute($entity, $arguments = [])
$productId = (int) $entity->getData($identifierField);
// prepare original data to compare
- $origPrices = $entity->getOrigData($attribute->getName());
+ $origPrices = [];
+ $originalId = $entity->getOrigData($identifierField);
+ if (empty($originalId) || $entity->getData($identifierField) == $originalId) {
+ $origPrices = $entity->getOrigData($attribute->getName());
+ }
+
$old = $this->prepareOriginalDataToCompare($origPrices, $isGlobal);
// prepare data for save
$new = $this->prepareNewDataForSave($priceRows, $isGlobal);
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
index f6d3ca36c1e..99edfe5bc72 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
@@ -11,6 +11,8 @@
use Magento\Framework\Exception\NoSuchEntityException;
/**
+ * Product attribute repository
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Repository implements \Magento\Catalog\Api\ProductAttributeRepositoryInterface
@@ -78,7 +80,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($attributeCode)
{
@@ -89,7 +91,7 @@ public function get($attributeCode)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
{
@@ -100,12 +102,17 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute)
{
+ $attribute->setEntityTypeId(
+ $this->eavConfig
+ ->getEntityType(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE)
+ ->getId()
+ );
if ($attribute->getAttributeId()) {
$existingModel = $this->get($attribute->getAttributeCode());
@@ -144,11 +151,6 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib
$attribute->setBackendModel(
$this->productHelper->getAttributeBackendModelByInputType($attribute->getFrontendInput())
);
- $attribute->setEntityTypeId(
- $this->eavConfig
- ->getEntityType(\Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE)
- ->getId()
- );
$attribute->setIsUserDefined(1);
}
if (!empty($attribute->getData(AttributeInterface::OPTIONS))) {
@@ -180,7 +182,7 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(\Magento\Catalog\Api\Data\ProductAttributeInterface $attribute)
{
@@ -189,7 +191,7 @@ public function delete(\Magento\Catalog\Api\Data\ProductAttributeInterface $attr
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteById($attributeCode)
{
@@ -200,7 +202,7 @@ public function deleteById($attributeCode)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getCustomAttributesMetadata($dataObjectClassName = null)
diff --git a/app/code/Magento/Catalog/Model/Product/Copier.php b/app/code/Magento/Catalog/Model/Product/Copier.php
index e94104ae473..ce6b4d98bbc 100644
--- a/app/code/Magento/Catalog/Model/Product/Copier.php
+++ b/app/code/Magento/Catalog/Model/Product/Copier.php
@@ -8,7 +8,11 @@
namespace Magento\Catalog\Model\Product;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Product;
+/**
+ * The copier creates product duplicates.
+ */
class Copier
{
/**
@@ -49,7 +53,7 @@ public function __construct(
* @param \Magento\Catalog\Model\Product $product
* @return \Magento\Catalog\Model\Product
*/
- public function copy(\Magento\Catalog\Model\Product $product)
+ public function copy(Product $product)
{
$product->getWebsiteIds();
$product->getCategoryIds();
@@ -79,6 +83,7 @@ public function copy(\Magento\Catalog\Model\Product $product)
? $matches[1] . '-' . ($matches[2] + 1)
: $urlKey . '-1';
$duplicate->setUrlKey($urlKey);
+ $duplicate->setData(Product::URL_PATH, null);
try {
$duplicate->save();
$isDuplicateSaved = true;
@@ -94,6 +99,8 @@ public function copy(\Magento\Catalog\Model\Product $product)
}
/**
+ * Returns product option repository.
+ *
* @return Option\Repository
* @deprecated 101.0.0
*/
@@ -107,6 +114,8 @@ private function getOptionRepository()
}
/**
+ * Returns metadata pool.
+ *
* @return \Magento\Framework\EntityManager\MetadataPool
* @deprecated 101.0.0
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
index 4d274a071d0..0e08b0af928 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/GalleryManagement.php
@@ -1,6 +1,5 @@
contentValidator->isValid($entryContent)) {
throw new InputException(__('The image content is invalid. Verify the content and try again.'));
}
- $product = $this->productRepository->get($sku);
+ $product = $this->productRepository->get($sku, true);
$existingMediaGalleryEntries = $product->getMediaGalleryEntries();
$existingEntryIds = [];
@@ -84,11 +86,11 @@ public function create($sku, ProductAttributeMediaGalleryEntryInterface $entry)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function update($sku, ProductAttributeMediaGalleryEntryInterface $entry)
{
- $product = $this->productRepository->get($sku);
+ $product = $this->productRepository->get($sku, true);
$existingMediaGalleryEntries = $product->getMediaGalleryEntries();
if ($existingMediaGalleryEntries == null) {
throw new NoSuchEntityException(
@@ -125,11 +127,11 @@ public function update($sku, ProductAttributeMediaGalleryEntryInterface $entry)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function remove($sku, $entryId)
{
- $product = $this->productRepository->get($sku);
+ $product = $this->productRepository->get($sku, true);
$existingMediaGalleryEntries = $product->getMediaGalleryEntries();
if ($existingMediaGalleryEntries == null) {
throw new NoSuchEntityException(
@@ -155,7 +157,7 @@ public function remove($sku, $entryId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($sku, $entryId)
{
@@ -176,7 +178,7 @@ public function get($sku, $entryId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList($sku)
{
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php
new file mode 100644
index 00000000000..c9afdf023b3
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ExistingValidate.php
@@ -0,0 +1,52 @@
+_messages = [];
+ $this->_errors = [];
+
+ if (!is_string($value)) {
+ $this->_messages[] = __('Full file path is expected.')->render();
+ return false;
+ }
+
+ $result = true;
+ $fileInfo = null;
+ if ($originalName) {
+ $fileInfo = ['name' => $originalName];
+ }
+ foreach ($this->_validators as $element) {
+ $validator = $element['instance'];
+ if ($validator->isValid($value, $fileInfo)) {
+ continue;
+ }
+ $result = false;
+ $messages = $validator->getMessages();
+ $this->_messages = array_merge($this->_messages, $messages);
+ $this->_errors = array_merge($this->_errors, array_keys($messages));
+ if ($element['breakChainOnFailure']) {
+ break;
+ }
+ }
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php
index 32c901afe8e..a7add0ad87b 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidateFactory.php
@@ -6,13 +6,18 @@
namespace Magento\Catalog\Model\Product\Option\Type\File;
+/**
+ * Class ValidateFactory. Creates Validator with type "ExistingValidate"
+ */
class ValidateFactory
{
/**
+ * Main factory method
+ *
* @return \Zend_Validate
*/
public function create()
{
- return new \Zend_Validate();
+ return new ExistingValidate();
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
index d6a5cb1cbc2..fef4999a117 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
@@ -10,8 +10,12 @@
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Catalog\Model\Product\Exception as ProductException;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Math\Random;
+use Magento\Framework\App\ObjectManager;
/**
+ * Validator class. Represents logic for validation file given from product option
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ValidatorFile extends Validator
@@ -63,11 +67,19 @@ class ValidatorFile extends Validator
protected $isImageValidator;
/**
+ * @var Random
+ */
+ private $random;
+
+ /**
+ * Constructor method
+ *
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\Framework\File\Size $fileSize
* @param \Magento\Framework\HTTP\Adapter\FileTransferFactory $httpFactory
* @param \Magento\Framework\Validator\File\IsImage $isImageValidator
+ * @param Random|null $random
* @throws \Magento\Framework\Exception\FileSystemException
*/
public function __construct(
@@ -75,16 +87,21 @@ public function __construct(
\Magento\Framework\Filesystem $filesystem,
\Magento\Framework\File\Size $fileSize,
\Magento\Framework\HTTP\Adapter\FileTransferFactory $httpFactory,
- \Magento\Framework\Validator\File\IsImage $isImageValidator
+ \Magento\Framework\Validator\File\IsImage $isImageValidator,
+ Random $random = null
) {
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->filesystem = $filesystem;
$this->httpFactory = $httpFactory;
$this->isImageValidator = $isImageValidator;
+ $this->random = $random
+ ?? ObjectManager::getInstance()->get(Random::class);
parent::__construct($scopeConfig, $filesystem, $fileSize);
}
/**
+ * Setter method for the product
+ *
* @param Product $product
* @return $this
*/
@@ -95,6 +112,8 @@ public function setProduct(Product $product)
}
/**
+ * Validation method
+ *
* @param \Magento\Framework\DataObject $processingParams
* @param \Magento\Catalog\Model\Product\Option $option
* @return array
@@ -154,8 +173,6 @@ public function validate($processingParams, $option)
$userValue = [];
if ($upload->isUploaded($file) && $upload->isValid($file)) {
- $extension = pathinfo(strtolower($fileInfo['name']), PATHINFO_EXTENSION);
-
$fileName = \Magento\MediaStorage\Model\File\Uploader::getCorrectFileName($fileInfo['name']);
$dispersion = \Magento\MediaStorage\Model\File\Uploader::getDispersionPath($fileName);
@@ -163,7 +180,8 @@ public function validate($processingParams, $option)
$tmpDirectory = $this->filesystem->getDirectoryRead(DirectoryList::SYS_TMP);
$fileHash = md5($tmpDirectory->readFile($tmpDirectory->getRelativePath($fileInfo['tmp_name'])));
- $filePath .= '/' . $fileHash . '.' . $extension;
+ $fileRandomName = $this->random->getRandomString(32);
+ $filePath .= '/' .$fileRandomName;
$fileFullPath = $this->mediaDirectory->getAbsolutePath($this->quotePath . $filePath);
$upload->addFilter(new \Zend_Filter_File_Rename(['target' => $fileFullPath, 'overwrite' => true]));
@@ -243,6 +261,8 @@ protected function initFilesystem()
}
/**
+ * Validate contents length method
+ *
* @return bool
* @todo need correctly name
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
index 37e4c7b310a..100ad37273c 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
@@ -6,6 +6,9 @@
namespace Magento\Catalog\Model\Product\Option\Type\File;
+/**
+ * Validator for existing files.
+ */
class ValidatorInfo extends Validator
{
/**
@@ -34,6 +37,8 @@ class ValidatorInfo extends Validator
protected $fileRelativePath;
/**
+ * Construct method
+ *
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\Framework\File\Size $fileSize
@@ -53,6 +58,8 @@ public function __construct(
}
/**
+ * Setter method for property "useQuotePath"
+ *
* @param mixed $useQuotePath
* @return $this
*/
@@ -63,6 +70,8 @@ public function setUseQuotePath($useQuotePath)
}
/**
+ * Validate method for the option value depends on an option
+ *
* @param array $optionValue
* @param \Magento\Catalog\Model\Product\Option $option
* @return bool
@@ -90,7 +99,7 @@ public function validate($optionValue, $option)
}
$result = false;
- if ($validatorChain->isValid($this->fileFullPath)) {
+ if ($validatorChain->isValid($this->fileFullPath, $optionValue['title'])) {
$result = $this->rootDirectory->isReadable($this->fileRelativePath)
&& isset($optionValue['secret_key'])
&& $this->buildSecretKey($this->fileRelativePath) == $optionValue['secret_key'];
@@ -109,6 +118,8 @@ public function validate($optionValue, $option)
}
/**
+ * Method for creation secret key for the given file
+ *
* @param string $fileRelativePath
* @return string
*/
@@ -118,6 +129,8 @@ protected function buildSecretKey($fileRelativePath)
}
/**
+ * Calculates path for the file
+ *
* @param array $optionValue
* @return void
*/
diff --git a/app/code/Magento/Catalog/Model/ProductCategoryList.php b/app/code/Magento/Catalog/Model/ProductCategoryList.php
index 5bbae772d5c..c3a88a505c5 100644
--- a/app/code/Magento/Catalog/Model/ProductCategoryList.php
+++ b/app/code/Magento/Catalog/Model/ProductCategoryList.php
@@ -80,7 +80,10 @@ public function getCategoryIds($productId)
Select::SQL_UNION_ALL
);
- $this->categoryIdList[$productId] = $this->productResource->getConnection()->fetchCol($unionSelect);
+ $this->categoryIdList[$productId] = array_map(
+ 'intval',
+ $this->productResource->getConnection()->fetchCol($unionSelect)
+ );
}
return $this->categoryIdList[$productId];
diff --git a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
index 13f0dbf79a1..b96aff148e7 100644
--- a/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
+++ b/app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php
@@ -58,8 +58,8 @@ public function getCollection(\Magento\Catalog\Model\Product $product, $type)
}
usort($sorterItems, function ($itemA, $itemB) {
- $posA = intval($itemA['position']);
- $posB = intval($itemB['position']);
+ $posA = (int)$itemA['position'];
+ $posB = (int)$itemB['position'];
return $posA <=> $posB;
});
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
index 0d62d120f80..14ae38667d8 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -1534,7 +1534,7 @@ public function addPriceData($customerGroupId = null, $websiteId = null)
/**
* Add attribute to filter
*
- * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute|string $attribute
+ * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute|string|array $attribute
* @param array $condition
* @param string $joinType
* @return $this
@@ -1809,7 +1809,8 @@ protected function _productLimitationJoinWebsite()
}
$conditions[] = $this->getConnection()->quoteInto(
'product_website.website_id IN(?)',
- $filters['website_ids']
+ $filters['website_ids'],
+ 'int'
);
} elseif (isset(
$filters['store_id']
@@ -1821,7 +1822,7 @@ protected function _productLimitationJoinWebsite()
) {
$joinWebsite = true;
$websiteId = $this->_storeManager->getStore($filters['store_id'])->getWebsiteId();
- $conditions[] = $this->getConnection()->quoteInto('product_website.website_id = ?', $websiteId);
+ $conditions[] = $this->getConnection()->quoteInto('product_website.website_id = ?', $websiteId, 'int');
}
$fromPart = $this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM);
@@ -2017,12 +2018,16 @@ protected function _applyProductLimitations()
$conditions = [
'cat_index.product_id=e.entity_id',
- $this->getConnection()->quoteInto('cat_index.store_id=?', $filters['store_id']),
+ $this->getConnection()->quoteInto('cat_index.store_id=?', $filters['store_id'], 'int'),
];
if (isset($filters['visibility']) && !isset($filters['store_table'])) {
- $conditions[] = $this->getConnection()->quoteInto('cat_index.visibility IN(?)', $filters['visibility']);
+ $conditions[] = $this->getConnection()->quoteInto(
+ 'cat_index.visibility IN(?)',
+ $filters['visibility'],
+ 'int'
+ );
}
- $conditions[] = $this->getConnection()->quoteInto('cat_index.category_id=?', $filters['category_id']);
+ $conditions[] = $this->getConnection()->quoteInto('cat_index.category_id=?', $filters['category_id'], 'int');
if (isset($filters['category_is_anchor'])) {
$conditions[] = $this->getConnection()->quoteInto('cat_index.is_parent=?', $filters['category_is_anchor']);
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php
index 123f358be40..77f67480619 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Image.php
@@ -12,6 +12,9 @@
use Magento\Framework\DB\Select;
use Magento\Framework\App\ResourceConnection;
+/**
+ * Class for retrieval of all product images
+ */
class Image
{
/**
@@ -73,15 +76,24 @@ public function getAllProductImages(): \Generator
/**
* Get the number of unique pictures of products
+ *
* @return int
*/
public function getCountAllProductImages(): int
{
- $select = $this->getVisibleImagesSelect()->reset('columns')->columns('count(*)');
+ $select = $this->getVisibleImagesSelect()
+ ->reset('columns')
+ ->reset('distinct')
+ ->columns(
+ new \Zend_Db_Expr('count(distinct value)')
+ );
+
return (int) $this->connection->fetchOne($select);
}
/**
+ * Return Select to fetch all products images
+ *
* @return Select
*/
private function getVisibleImagesSelect(): Select
diff --git a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php
index 54a13be864d..77368517a31 100644
--- a/app/code/Magento/Catalog/Pricing/Price/BasePrice.php
+++ b/app/code/Magento/Catalog/Pricing/Price/BasePrice.php
@@ -30,7 +30,7 @@ public function getValue()
$this->value = false;
foreach ($this->priceInfo->getPrices() as $price) {
if ($price instanceof BasePriceProviderInterface && $price->getValue() !== false) {
- $this->value = min($price->getValue(), $this->value ?: $price->getValue());
+ $this->value = min($price->getValue(), $this->value !== false ? $this->value: $price->getValue());
}
}
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index 9f8d827b208..1f6c2ab4bb2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -257,11 +257,10 @@
-
-
+
+
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
index 23687484f10..903526a8622 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
@@ -20,4 +20,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
index c980c43b8f3..301bd4b7a57 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
@@ -15,9 +15,10 @@
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
index a46d40c62c7..a2bdaa7dbc6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
@@ -16,4 +16,8 @@
0
attributeTwo
+
+ 0
+ attributeThree
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml
new file mode 100644
index 00000000000..a2391dda548
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageData.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+ adobe-base.jpg
+ adobe-base
+ jpg
+
+
+
+ adobe-small.jpg
+ adobe-small
+ jpg
+
+
+
+ adobe-thumb.jpg
+ adobe-thumb
+ jpg
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 7c0cc03186c..6f07c0b9eab 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -420,4 +420,17 @@
1
EavStock100
+
+ api-simple-product
+ simple
+ 4
+ 4
+ Api Simple Product
+ 123.00
+ api-simple-product
+ 1
+ 1
+ EavStock1
+ CustomAttributeProductAttribute
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
index 6e532637fb6..e9e9e437523 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
@@ -17,4 +17,7 @@
Qty_10
+
+ Qty_1
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
index 39ecc2d440f..7cba4c3c76f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
@@ -28,4 +28,8 @@
101
true
+
+ 1
+ true
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
index 5aaa78822af..75e3210cad7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
index 8b69a44993f..9a0dd8f5b38 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
@@ -32,6 +32,13 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
index 6b754dcc5d4..40ea919226d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryProductsGridSection.xml
@@ -14,5 +14,6 @@
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
index 17778c76da9..78d06afa7f0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
@@ -23,6 +23,9 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml
new file mode 100644
index 00000000000..0f438540603
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeOptionsSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagePlaceholderConfigSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagePlaceholderConfigSection.xml
new file mode 100644
index 00000000000..7558b13d624
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagePlaceholderConfigSection.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml
new file mode 100644
index 00000000000..ff2e5f2f360
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMiniCartSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
index d8f6bc3e4a7..d38a8e4190c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
@@ -50,9 +50,9 @@
-
+
-
+
@@ -63,7 +63,7 @@
-
+
@@ -75,10 +75,12 @@
+
+
-
+
@@ -87,7 +89,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml
new file mode 100644
index 00000000000..4d97dee56f0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $getSmallPlaceholderImageSrc
+ {{placeholderSmallImage.name}}
+
+
+
+
+
+ $getSmallNonPlaceholderImageSrc
+ {{placeholderSmallImage.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $getThumbnailPlaceholderImageSrc
+ {{placeholderThumbnailImage.name}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $getThumbnailImageSrc
+ {{placeholderThumbnailImage.name}}
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml
index bfb95579106..8806612c0f5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest.xml
@@ -69,4 +69,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml
new file mode 100644
index 00000000000..525f81de6c4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeTest.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml
new file mode 100644
index 00000000000..a4bd507d98f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilteringCategoryProductsUsingScopeSelectorTest.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php
index 8a42865a3fe..95b06e40602 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ImageFactoryTest.php
@@ -145,7 +145,8 @@ private function getTestDataWithoutAttributes(): array
'label' => 'test_image_label',
'ratio' => 1,
'custom_attributes' => '',
- 'product_id' => null
+ 'product_id' => null,
+ 'class' => 'product-image-photo'
],
],
];
@@ -190,6 +191,7 @@ private function getTestDataWithAttributes(): array
'custom_attributes' => [
'name_1' => 'value_1',
'name_2' => 'value_2',
+ 'class' => 'my-class'
],
],
'expected' => [
@@ -201,7 +203,8 @@ private function getTestDataWithAttributes(): array
'label' => 'test_product_name',
'ratio' => 0.5, // <==
'custom_attributes' => 'name_1="value_1" name_2="value_2"',
- 'product_id' => null
+ 'product_id' => null,
+ 'class' => 'my-class'
],
],
];
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php
index ff44a91a649..c889c58e3df 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php
@@ -95,6 +95,11 @@ class HelperTest extends \PHPUnit\Framework\TestCase
*/
protected $attributeFilterMock;
+ /**
+ * @var \PHPUnit_Framework_MockObject_MockObject
+ */
+ private $dateTimeFilterMock;
+
/**
* @inheritdoc
*/
@@ -170,6 +175,11 @@ protected function setUp()
$resolverProperty = $helperReflection->getProperty('linkResolver');
$resolverProperty->setAccessible(true);
$resolverProperty->setValue($this->helper, $this->linkResolverMock);
+
+ $this->dateTimeFilterMock = $this->createMock(\Magento\Framework\Stdlib\DateTime\Filter\DateTime::class);
+ $dateTimeFilterProperty = $helperReflection->getProperty('dateTimeFilter');
+ $dateTimeFilterProperty->setAccessible(true);
+ $dateTimeFilterProperty->setValue($this->helper, $this->dateTimeFilterMock);
}
/**
@@ -211,6 +221,12 @@ public function testInitialize(
if (!empty($tierPrice)) {
$productData = array_merge($productData, ['tier_price' => $tierPrice]);
}
+
+ $this->dateTimeFilterMock->expects($this->once())
+ ->method('filter')
+ ->with($specialFromDate)
+ ->willReturn($specialFromDate);
+
$attributeNonDate = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Eav\Attribute::class)
->disableOriginalConstructor()
->getMock();
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php
index 3572cb9d87f..cce00c50d37 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/UpdateHandlerTest.php
@@ -120,8 +120,13 @@ public function testExecute(): void
['entity_id', $productId]
]
);
- $product->expects($this->atLeastOnce())->method('getOrigData')->with('tier_price')
- ->willReturn($originalTierPrices);
+ $product->expects($this->atLeastOnce())->method('getOrigData')
+ ->willReturnMap(
+ [
+ ['tier_price', $originalTierPrices],
+ ['entity_id', $productId]
+ ]
+ );
$product->expects($this->atLeastOnce())->method('getStoreId')->willReturn(0);
$product->expects($this->atLeastOnce())->method('setData')->with('tier_price_changed', 1);
$store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php
index c989f2dd474..6552e854400 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ImageUploaderTest.php
@@ -69,10 +69,17 @@ class ImageUploaderTest extends \PHPUnit\Framework\TestCase
/**
* Allowed extensions
*
- * @var string
+ * @var array
*/
private $allowedExtensions;
+ /**
+ * Allowed mime types
+ *
+ * @var array
+ */
+ private $allowedMimeTypes;
+
protected function setUp()
{
$this->coreFileStorageDatabaseMock = $this->createMock(
@@ -97,6 +104,7 @@ protected function setUp()
$this->baseTmpPath = 'base/tmp/';
$this->basePath = 'base/real/';
$this->allowedExtensions = ['.jpg'];
+ $this->allowedMimeTypes = ['image/jpg', 'image/jpeg', 'image/gif', 'image/png'];
$this->imageUploader =
new \Magento\Catalog\Model\ImageUploader(
@@ -107,7 +115,8 @@ protected function setUp()
$this->loggerMock,
$this->baseTmpPath,
$this->basePath,
- $this->allowedExtensions
+ $this->allowedExtensions,
+ $this->allowedMimeTypes
);
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
index 31fd0696db3..e9eee5c7668 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/CopierTest.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Api\Data\ProductInterface;
use \Magento\Catalog\Model\Product\Copier;
+use Magento\Catalog\Model\Product;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -54,7 +55,7 @@ protected function setUp()
\Magento\Catalog\Model\Product\Option\Repository::class
);
$this->optionRepositoryMock;
- $this->productMock = $this->createMock(\Magento\Catalog\Model\Product::class);
+ $this->productMock = $this->createMock(Product::class);
$this->productMock->expects($this->any())->method('getEntityId')->willReturn(1);
$this->metadata = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadata::class)
@@ -106,7 +107,7 @@ public function testCopy()
$this->productMock->expects($this->once())->method('getResource')->will($this->returnValue($resourceMock));
$duplicateMock = $this->createPartialMock(
- \Magento\Catalog\Model\Product::class,
+ Product::class,
[
'__wakeup',
'setData',
@@ -147,10 +148,10 @@ public function testCopy()
)->with(
\Magento\Store\Model\Store::DEFAULT_STORE_ID
);
- $duplicateMock->expects($this->once())->method('setData')->with($productData);
+ $duplicateMock->expects($this->atLeastOnce())->method('setData')->willReturn($duplicateMock);
$this->copyConstructorMock->expects($this->once())->method('build')->with($this->productMock, $duplicateMock);
$duplicateMock->expects($this->once())->method('getUrlKey')->willReturn('urk-key-1');
- $duplicateMock->expects($this->once())->method('setUrlKey')->with('urk-key-2');
+ $duplicateMock->expects($this->once())->method('setUrlKey')->with('urk-key-2')->willReturn($duplicateMock);
$duplicateMock->expects($this->once())->method('save');
$this->metadata->expects($this->any())->method('getLinkField')->willReturn('linkField');
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
index 9fafbc9d967..1d12645019d 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Gallery/GalleryManagementTest.php
@@ -266,7 +266,7 @@ public function testGetWithNonExistingProduct()
/**
* @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedExceptionText The image doesn't exist. Verify and try again.
+ * @expectedExceptionMessage The image doesn't exist. Verify and try again.
*/
public function testGetWithNonExistingImage()
{
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php
index 754d80302d4..6029a2b8200 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/PriceModifierTest.php
@@ -54,7 +54,7 @@ protected function setUp()
/**
* @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedMessage Tier price is unavailable for this product.
+ * @expectedExceptionMessage Product hasn't group price with such data: customerGroupId = '1', website = 1, qty = 3
*/
public function testRemoveWhenTierPricesNotExists()
{
@@ -70,7 +70,7 @@ public function testRemoveWhenTierPricesNotExists()
/**
* @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedMessage For current customerGroupId = '10' with 'qty' = 15 any tier price exist'.
+ * @expectedExceptionMessage Product hasn't group price with such data: customerGroupId = '10', website = 1, qty = 5
*/
public function testRemoveTierPriceForNonExistingCustomerGroup()
{
@@ -81,7 +81,7 @@ public function testRemoveTierPriceForNonExistingCustomerGroup()
->will($this->returnValue($this->prices));
$this->productMock->expects($this->never())->method('setData');
$this->productRepositoryMock->expects($this->never())->method('save');
- $this->priceModifier->removeTierPrice($this->productMock, 10, 15, 1);
+ $this->priceModifier->removeTierPrice($this->productMock, 10, 5, 1);
}
public function testSuccessfullyRemoveTierPriceSpecifiedForAllGroups()
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php
index f340d0b204b..ae479a9b34d 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/TierPriceManagementTest.php
@@ -195,7 +195,7 @@ public function testSuccessDeleteTierPrice()
/**
* @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @message The product doesn't exist. Verify and try again.
+ * @expectedExceptionMessage No such entity.
*/
public function testDeleteTierPriceFromNonExistingProduct()
{
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php
new file mode 100644
index 00000000000..4fce12dc2de
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/ImageTest.php
@@ -0,0 +1,237 @@
+objectManager =
+ new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+ $this->connectionMock = $this->createMock(AdapterInterface::class);
+ $this->resourceMock = $this->createMock(ResourceConnection::class);
+ $this->resourceMock->method('getConnection')
+ ->willReturn($this->connectionMock);
+ $this->resourceMock->method('getTableName')
+ ->willReturnArgument(0);
+ $this->generatorMock = $this->createMock(Generator::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ protected function getVisibleImagesSelectMock(): MockObject
+ {
+ $selectMock = $this->getMockBuilder(Select::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $selectMock->expects($this->once())
+ ->method('distinct')
+ ->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('from')
+ ->with(
+ ['images' => Gallery::GALLERY_TABLE],
+ 'value as filepath'
+ )->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('where')
+ ->with('disabled = 0')
+ ->willReturnSelf();
+
+ return $selectMock;
+ }
+
+ /**
+ * @param int $imagesCount
+ * @dataProvider dataProvider
+ */
+ public function testGetCountAllProductImages(int $imagesCount): void
+ {
+ $selectMock = $this->getVisibleImagesSelectMock();
+ $selectMock->expects($this->exactly(2))
+ ->method('reset')
+ ->withConsecutive(
+ ['columns'],
+ ['distinct']
+ )->willReturnSelf();
+ $selectMock->expects($this->once())
+ ->method('columns')
+ ->with(new \Zend_Db_Expr('count(distinct value)'))
+ ->willReturnSelf();
+
+ $this->connectionMock->expects($this->once())
+ ->method('select')
+ ->willReturn($selectMock);
+ $this->connectionMock->expects($this->once())
+ ->method('fetchOne')
+ ->with($selectMock)
+ ->willReturn($imagesCount);
+
+ $imageModel = $this->objectManager->getObject(
+ Image::class,
+ [
+ 'generator' => $this->generatorMock,
+ 'resourceConnection' => $this->resourceMock
+ ]
+ );
+
+ $this->assertSame(
+ $imagesCount,
+ $imageModel->getCountAllProductImages()
+ );
+ }
+
+ /**
+ * @param int $imagesCount
+ * @param int $batchSize
+ * @dataProvider dataProvider
+ */
+ public function testGetAllProductImages(
+ int $imagesCount,
+ int $batchSize
+ ): void {
+ $this->connectionMock->expects($this->once())
+ ->method('select')
+ ->willReturn($this->getVisibleImagesSelectMock());
+
+ $batchCount = (int)ceil($imagesCount / $batchSize);
+ $fetchResultsCallback = $this->getFetchResultCallbackForBatches($imagesCount, $batchSize);
+ $this->connectionMock->expects($this->exactly($batchCount))
+ ->method('fetchAll')
+ ->will($this->returnCallback($fetchResultsCallback));
+
+ /** @var Select | MockObject $selectMock */
+ $selectMock = $this->getMockBuilder(Select::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->generatorMock->expects($this->once())
+ ->method('generate')
+ ->with(
+ 'value_id',
+ $selectMock,
+ $batchSize,
+ BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR
+ )->will(
+ $this->returnCallback(
+ $this->getBatchIteratorCallback($selectMock, $batchCount)
+ )
+ );
+
+ $imageModel = $this->objectManager->getObject(
+ Image::class,
+ [
+ 'generator' => $this->generatorMock,
+ 'resourceConnection' => $this->resourceMock,
+ 'batchSize' => $batchSize
+ ]
+ );
+
+ $this->assertCount($imagesCount, $imageModel->getAllProductImages());
+ }
+
+ /**
+ * @param int $imagesCount
+ * @param int $batchSize
+ * @return \Closure
+ */
+ protected function getFetchResultCallbackForBatches(
+ int $imagesCount,
+ int $batchSize
+ ): \Closure {
+ $fetchResultsCallback = function () use (&$imagesCount, $batchSize) {
+ $batchSize =
+ ($imagesCount >= $batchSize) ? $batchSize : $imagesCount;
+ $imagesCount -= $batchSize;
+
+ $getFetchResults = function ($batchSize): array {
+ $result = [];
+ $count = $batchSize;
+ while ($count) {
+ $count--;
+ $result[$count] = $count;
+ }
+
+ return $result;
+ };
+
+ return $getFetchResults($batchSize);
+ };
+
+ return $fetchResultsCallback;
+ }
+
+ /**
+ * @param Select | MockObject $selectMock
+ * @param int $batchCount
+ * @return \Closure
+ */
+ protected function getBatchIteratorCallback(
+ MockObject $selectMock,
+ int $batchCount
+ ): \Closure {
+ $iteratorCallback = function () use ($batchCount, $selectMock): array {
+ $result = [];
+ $count = $batchCount;
+ while ($count) {
+ $count--;
+ $result[$count] = $selectMock;
+ }
+
+ return $result;
+ };
+
+ return $iteratorCallback;
+ }
+
+ /**
+ * Data Provider
+ * @return array
+ */
+ public function dataProvider(): array
+ {
+ return [
+ [300, 300],
+ [300, 100],
+ [139, 100],
+ [67, 10],
+ [154, 47],
+ [0, 100]
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
index 98de8ea3476..6ec1cc6c46d 100755
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
@@ -58,8 +58,11 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * Customize number fields for advanced price and weight fields.
+ *
* @since 101.0.0
+ * @param array $data
+ * @return array
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function modifyData(array $data)
@@ -130,8 +133,11 @@ protected function customizeAdvancedPriceFormat(array $data)
}
/**
- * {@inheritdoc}
+ * Customize product form fields.
+ *
* @since 101.0.0
+ * @param array $meta
+ * @return array
*/
public function modifyMeta(array $meta)
{
@@ -361,7 +367,8 @@ protected function customizeNameListeners(array $meta)
$skuPath . static::META_CONFIG_PATH,
$meta,
[
- 'autoImportIfEmpty' => true
+ 'autoImportIfEmpty' => true,
+ 'validation' => ['no-marginal-whitespace' => true]
]
);
diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml
index 860e5fee9f1..a366065c89e 100644
--- a/app/code/Magento/Catalog/etc/db_schema.xml
+++ b/app/code/Magento/Catalog/etc/db_schema.xml
@@ -22,13 +22,13 @@
comment="Creation Time"/>
-
+
-
+
-
+
@@ -43,27 +43,27 @@
-
+
-
-
-
-
+
-
+
-
+
@@ -79,27 +79,27 @@
default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -115,27 +115,27 @@
default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -150,27 +150,27 @@
-
+
-
-
-
-
+
-
+
-
+
@@ -185,27 +185,27 @@
-
+
-
-
-
-
+
-
+
-
+
@@ -222,30 +222,30 @@
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -267,13 +267,13 @@
comment="Tree Level"/>
-
+
-
+
-
+
@@ -288,30 +288,30 @@
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -327,30 +327,30 @@
default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -366,30 +366,30 @@
default="0" comment="Entity ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -404,30 +404,30 @@
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -442,30 +442,30 @@
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -479,22 +479,22 @@
default="0" comment="Product ID"/>
-
+
-
-
-
+
-
+
@@ -512,18 +512,18 @@
default="0" comment="Store ID"/>
-
+
-
+
-
+
@@ -542,29 +542,29 @@
default="0" comment="Product ID"/>
-
+
-
-
-
-
+
-
+
-
+
-
+
@@ -574,17 +574,17 @@
comment="Product ID"/>
-
+
-
-
-
+
@@ -593,7 +593,7 @@
-
+
@@ -607,27 +607,27 @@
default="0" comment="Linked Product ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -640,13 +640,13 @@
-
+
-
-
+
@@ -660,21 +660,21 @@
comment="Link ID"/>
-
+
-
-
-
+
-
+
@@ -688,21 +688,21 @@
comment="Link ID"/>
-
+
-
-
-
+
-
+
@@ -715,21 +715,21 @@
-
+
-
-
-
+
-
+
@@ -751,29 +751,29 @@
comment="Website ID"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -788,13 +788,13 @@
comment="Media entry type"/>
-
+
-
-
+
@@ -813,26 +813,26 @@
default="0" comment="Is Disabled"/>
-
+
-
-
-
-
+
-
+
-
+
@@ -854,13 +854,13 @@
comment="Image Size Y"/>
-
+
-
-
+
@@ -875,20 +875,20 @@
-
+
-
-
-
+
-
+
@@ -901,20 +901,20 @@
-
+
-
-
-
+
-
+
@@ -927,13 +927,13 @@
-
+
-
-
+
@@ -948,21 +948,21 @@
-
+
-
-
-
+
-
+
@@ -975,21 +975,21 @@
-
+
-
-
-
+
-
+
@@ -1037,16 +1037,16 @@
identity="false" default="0" comment="Is Visible in Grid"/>
-
+
-
-
+
-
+
@@ -1055,17 +1055,17 @@
comment="Parent ID"/>
-
+
-
-
-
+
@@ -1081,20 +1081,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1110,20 +1110,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1147,18 +1147,18 @@
comment="Max Price"/>
-
+
-
+
-
+
-
+
@@ -1174,24 +1174,24 @@
comment="Website ID"/>
-
+
-
-
-
-
+
-
+
@@ -1203,12 +1203,12 @@
comment="Default store id for website"/>
-
+
-
-
+
@@ -1226,7 +1226,7 @@
comment="Price"/>
-
+
@@ -1247,7 +1247,7 @@
comment="Price"/>
-
+
@@ -1268,7 +1268,7 @@
comment="Max Price"/>
-
+
@@ -1288,7 +1288,7 @@
comment="Max Price"/>
-
+
@@ -1316,7 +1316,7 @@
comment="Tier Price"/>
-
+
@@ -1344,7 +1344,7 @@
comment="Tier Price"/>
-
+
@@ -1364,7 +1364,7 @@
comment="Max Price"/>
-
+
@@ -1384,7 +1384,7 @@
comment="Max Price"/>
-
+
@@ -1406,7 +1406,7 @@
comment="Max Price"/>
-
+
@@ -1429,7 +1429,7 @@
comment="Max Price"/>
-
+
@@ -1448,20 +1448,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1477,20 +1477,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1506,20 +1506,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1535,20 +1535,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1572,18 +1572,18 @@
comment="Max Price"/>
-
+
-
+
-
+
-
+
@@ -1607,18 +1607,18 @@
comment="Max Price"/>
-
+
-
+
-
+
-
+
@@ -1636,12 +1636,12 @@
default="0" comment="Store ID"/>
-
+
-
+
@@ -1653,14 +1653,14 @@
comment="Value media Entry ID"/>
-
-
-
+
@@ -1677,20 +1677,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1706,20 +1706,20 @@
comment="Value"/>
-
+
-
+
-
+
-
+
@@ -1743,18 +1743,18 @@
comment="Max Price"/>
-
+
-
+
-
+
-
+
@@ -1774,18 +1774,18 @@
default="0" comment="Store ID"/>
-
+
-
+
-
+
@@ -1806,21 +1806,21 @@
comment="Product Id"/>
-
+
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index d12ae8f821e..a802496d299 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -220,6 +220,12 @@
- gif
- png
+
+ - image/jpg
+ - image/jpeg
+ - image/gif
+ - image/png
+
diff --git a/app/code/Magento/Catalog/i18n/en_US.csv b/app/code/Magento/Catalog/i18n/en_US.csv
index f2a3ab8f83f..ed27dfd646c 100644
--- a/app/code/Magento/Catalog/i18n/en_US.csv
+++ b/app/code/Magento/Catalog/i18n/en_US.csv
@@ -233,7 +233,6 @@ Products,Products
"This attribute set no longer exists.","This attribute set no longer exists."
"You saved the attribute set.","You saved the attribute set."
"Something went wrong while saving the attribute set.","Something went wrong while saving the attribute set."
-"You added product %1 to the comparison list.","You added product %1 to the comparison list."
"You cleared the comparison list.","You cleared the comparison list."
"Something went wrong clearing the comparison list.","Something went wrong clearing the comparison list."
"You removed product %1 from the comparison list.","You removed product %1 from the comparison list."
@@ -808,4 +807,5 @@ Details,Details
"Product Name or SKU", "Product Name or SKU"
"Start typing to find products", "Start typing to find products"
"Product with ID: (%1) doesn't exist", "Product with ID: (%1) doesn't exist"
-"Category with ID: (%1) doesn't exist", "Category with ID: (%1) doesn't exist"
\ No newline at end of file
+"Category with ID: (%1) doesn't exist", "Category with ID: (%1) doesn't exist"
+"You added product %1 to the comparison list .","You added product %1 to the comparison list ."
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml
index 223b3e9888e..75f04eae821 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/set/main/tree/attribute.phtml
@@ -2,4 +2,4 @@
/**
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
- */;
+ */
diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml
index dafea71f872..1a54db0d59f 100644
--- a/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/category_form.xml
@@ -331,7 +331,6 @@
- group
-
- true
- - true
@@ -341,6 +340,9 @@
+