Skip to content

Commit b7e5bcf

Browse files
authored
Merge branch '2.4-develop' into refactor/mftf-customer
2 parents df511be + 79ab7c8 commit b7e5bcf

File tree

320 files changed

+9250
-1599
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

320 files changed

+9250
-1599
lines changed

app/code/Magento/Backend/Block/Widget/Grid/Column/Renderer/AbstractRenderer.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
use Magento\Framework\DataObject;
1010

1111
/**
12+
* Produce html output using the given data source.
13+
*
14+
* phpcs:disable Magento2.Classes.AbstractApi
1215
* Backend grid item abstract renderer
1316
* @api
1417
* @SuppressWarnings(PHPMD.NumberOfChildren)
@@ -53,7 +56,7 @@ public function getColumn()
5356
* Renders grid column
5457
*
5558
* @param DataObject $row
56-
* @return string
59+
* @return string
5760
*/
5861
public function render(DataObject $row)
5962
{
@@ -62,7 +65,7 @@ public function render(DataObject $row)
6265
$result .= $this->getColumn()->getEditOnly() ? ''
6366
: '<span class="admin__grid-control-value">' . $this->_getValue($row) . '</span>';
6467

65-
return $result . $this->_getInputValueElement($row) . '</div>' ;
68+
return $result . $this->_getInputValueElement($row) . '</div>';
6669
}
6770
return $this->_getValue($row);
6871
}
@@ -90,6 +93,7 @@ protected function _getValue(DataObject $row)
9093
if (is_string($getter)) {
9194
return $row->{$getter}();
9295
} elseif (is_callable($getter)) {
96+
//phpcs:ignore Magento2.Functions.DiscouragedFunction
9397
return call_user_func($getter, $row);
9498
}
9599
return '';

app/code/Magento/Backend/Test/Mftf/ActionGroup/LoginActionGroup.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
1111
<actionGroup name="LoginActionGroup">
1212
<annotations>
13-
<description>Login to Backend Admin using ENV Admin credentials. PLEASE NOTE: This Action Group does NOT validate that you are Logged In.</description>
13+
<description>DEPRECATED. Please use LoginAsAdmin instead.
14+
Login to Backend Admin using ENV Admin credentials. PLEASE NOTE: This Action Group does NOT validate that you are Logged In.</description>
1415
</annotations>
1516

1617
<amOnPage url="{{_ENV.MAGENTO_BACKEND_NAME}}" stepKey="navigateToAdmin"/>

app/code/Magento/Backend/etc/adminhtml/system.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,15 @@
115115
<label>Enable Template Path Hints for Storefront</label>
116116
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
117117
</field>
118-
<field id="template_hints_storefront_show_with_parameter" translate="label" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
118+
<field id="template_hints_storefront_show_with_parameter" translate="label comment" type="select" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1">
119119
<label>Enable Hints for Storefront with URL Parameter</label>
120120
<depends>
121121
<field id="*/*/template_hints_storefront">1</field>
122122
</depends>
123123
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
124124
<comment>Use URL parameter to enable template path hints for Storefront</comment>
125125
</field>
126-
<field id="template_hints_parameter_value" translate="label" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
126+
<field id="template_hints_parameter_value" translate="label comment" type="text" sortOrder="20" showInDefault="1" showInWebsite="1" showInStore="1" canRestore="1">
127127
<label>Parameter Value</label>
128128
<depends>
129129
<field id="*/*/template_hints_storefront">1</field>

app/code/Magento/Backend/i18n/en_US.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,3 +461,5 @@ Pagination,Pagination
461461
"Alternative text for the next pages link in the pagination menu. If empty, default arrow image is used.","Alternative text for the next pages link in the pagination menu. If empty, default arrow image is used."
462462
"Anchor Text for Next","Anchor Text for Next"
463463
"Theme Name","Theme Name"
464+
"Use URL parameter to enable template path hints for Storefront","Use URL parameter to enable template path hints for Storefront"
465+
"Add the following parameter to the URL to show template hints ?templatehints=[parameter_value]","Add the following parameter to the URL to show template hints ?templatehints=[parameter_value]"

app/code/Magento/Braintree/view/frontend/web/js/view/payment/3d-secure.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ define([
117117
options.bin = context.paymentPayload.details.bin;
118118
}
119119

120-
if (shippingAddress) {
120+
if (shippingAddress && this.isValidShippingAddress(shippingAddress)) {
121121
options.additionalInformation = {
122122
shippingGivenName: shippingAddress.firstname,
123123
shippingSurname: shippingAddress.lastname,
@@ -206,6 +206,25 @@ define([
206206
}
207207

208208
return false;
209+
},
210+
211+
/**
212+
* Validate shipping address
213+
*
214+
* @param {Object} shippingAddress
215+
* @return {Boolean}
216+
*/
217+
isValidShippingAddress: function (shippingAddress) {
218+
var isValid = false;
219+
220+
// check that required fields are not empty
221+
if (shippingAddress.firstname && shippingAddress.lastname && shippingAddress.telephone &&
222+
shippingAddress.street && shippingAddress.city && shippingAddress.regionCode &&
223+
shippingAddress.postcode && shippingAddress.countryId) {
224+
isValid = true;
225+
}
226+
227+
return isValid;
209228
}
210229
};
211230
});

app/code/Magento/Braintree/view/frontend/web/js/view/payment/method-renderer/cc-form.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ define(
9191
})
9292
.then(function (hostedFieldsInstance) {
9393
self.hostedFieldsInstance = hostedFieldsInstance;
94-
self.isPlaceOrderActionAllowed(true);
94+
self.isPlaceOrderActionAllowed(false);
9595
self.initFormValidationEvents(hostedFieldsInstance);
9696

9797
return self.hostedFieldsInstance;

app/code/Magento/BundleGraphQl/Model/Resolver/Options/Collection.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,7 @@ public function addParentFilterData(int $parentId, int $parentEntityId, string $
7878
public function getOptionsByParentId(int $parentId) : array
7979
{
8080
$options = $this->fetch();
81-
if (!isset($options[$parentId])) {
82-
return [];
83-
}
84-
85-
return $options[$parentId];
81+
return $options[$parentId] ?? [];
8682
}
8783

8884
/**
@@ -115,7 +111,7 @@ private function fetch() : array
115111

116112
$this->extensionAttributesJoinProcessor->process($optionsCollection);
117113
if (empty($optionsCollection->getData())) {
118-
return null;
114+
return [];
119115
}
120116

121117
/** @var \Magento\Bundle\Model\Option $option */
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Captcha\Test\Unit\Observer;
10+
11+
use Magento\Captcha\Model\ResourceModel\Log;
12+
use Magento\Captcha\Model\ResourceModel\LogFactory;
13+
use Magento\Captcha\Observer\ResetAttemptForBackendObserver;
14+
use Magento\Framework\Event;
15+
use Magento\Framework\Event\Observer;
16+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
17+
use PHPUnit\Framework\MockObject\MockObject;
18+
use PHPUnit\Framework\TestCase;
19+
20+
/**
21+
* Unit test for \Magento\Captcha\Observer\ResetAttemptForBackendObserver
22+
*/
23+
class ResetAttemptForBackendObserverTest extends TestCase
24+
{
25+
/**
26+
* Test that the method resets attempts for Backend
27+
*/
28+
public function testExecuteExpectsDeleteUserAttemptsCalled()
29+
{
30+
$logMock = $this->createMock(Log::class);
31+
$logMock->expects($this->once())->method('deleteUserAttempts')->willReturnSelf();
32+
33+
$resLogFactoryMock = $this->createMock(LogFactory::class);
34+
$resLogFactoryMock->expects($this->once())
35+
->method('create')
36+
->willReturn($logMock);
37+
38+
/** @var MockObject|Observer $eventObserverMock */
39+
$eventObserverMock = $this->createPartialMock(Observer::class, ['getUser']);
40+
$eventMock = $this->createMock(Event::class);
41+
$eventObserverMock->expects($this->once())
42+
->method('getUser')
43+
->willReturn($eventMock);
44+
45+
$objectManager = new ObjectManagerHelper($this);
46+
/** @var ResetAttemptForBackendObserver $observer */
47+
$observer = $objectManager->getObject(
48+
ResetAttemptForBackendObserver::class,
49+
['resLogFactory' => $resLogFactoryMock]
50+
);
51+
$this->assertInstanceOf(Log::class, $observer->execute($eventObserverMock));
52+
}
53+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\Captcha\Test\Unit\Observer;
10+
11+
use Magento\Captcha\Model\ResourceModel\Log;
12+
use Magento\Captcha\Model\ResourceModel\LogFactory;
13+
use Magento\Captcha\Observer\ResetAttemptForFrontendObserver;
14+
use Magento\Customer\Model\Customer;
15+
use Magento\Framework\Event\Observer;
16+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
17+
use PHPUnit\Framework\MockObject\MockObject;
18+
use PHPUnit\Framework\TestCase;
19+
20+
/**
21+
* Unit test for \Magento\Captcha\Observer\ResetAttemptForFrontendObserver
22+
*/
23+
class ResetAttemptForFrontendObserverTest extends TestCase
24+
{
25+
/**
26+
* Test that the method resets attempts for Frontend
27+
*/
28+
public function testExecuteExpectsDeleteUserAttemptsCalled()
29+
{
30+
$logMock = $this->createMock(Log::class);
31+
$logMock->expects($this->once())->method('deleteUserAttempts')->willReturnSelf();
32+
33+
$resLogFactoryMock = $this->createMock(LogFactory::class);
34+
$resLogFactoryMock->expects($this->once())
35+
->method('create')
36+
->willReturn($logMock);
37+
38+
/** @var MockObject|Observer $eventObserverMock */
39+
$eventObserverMock = $this->createPartialMock(Observer::class, ['getModel']);
40+
$eventObserverMock->expects($this->once())
41+
->method('getModel')
42+
->willReturn($this->createMock(Customer::class));
43+
44+
$objectManager = new ObjectManagerHelper($this);
45+
/** @var ResetAttemptForFrontendObserver $observer */
46+
$observer = $objectManager->getObject(
47+
ResetAttemptForFrontendObserver::class,
48+
['resLogFactory' => $resLogFactoryMock]
49+
);
50+
$this->assertInstanceOf(Log::class, $observer->execute($eventObserverMock));
51+
}
52+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
namespace Magento\Catalog\Api;
8+
9+
/**
10+
* @api
11+
* @since 100.0.2
12+
*/
13+
interface CategoryListDeleteBySkuInterface
14+
{
15+
/**
16+
* Delete by skus list
17+
*
18+
* @param int $categoryId
19+
* @param string[] $productSkuList
20+
* @return bool
21+
*
22+
* @throws \Magento\Framework\Exception\CouldNotSaveException
23+
* @throws \Magento\Framework\Exception\NoSuchEntityException
24+
* @throws \Magento\Framework\Exception\InputException
25+
*/
26+
public function deleteBySkus(int $categoryId, array $productSkuList): bool;
27+
}

app/code/Magento/Catalog/Model/CategoryLinkRepository.php

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,52 @@
66

77
namespace Magento\Catalog\Model;
88

9-
use Magento\Framework\Exception\InputException;
9+
use Magento\Catalog\Api\CategoryLinkRepositoryInterface;
10+
use Magento\Catalog\Api\CategoryListDeleteBySkuInterface;
11+
use Magento\Catalog\Api\CategoryRepositoryInterface;
12+
use Magento\Catalog\Api\ProductRepositoryInterface;
13+
use Magento\Catalog\Model\ResourceModel\Product;
14+
use Magento\Framework\App\ObjectManager;
1015
use Magento\Framework\Exception\CouldNotSaveException;
16+
use Magento\Framework\Exception\InputException;
1117

12-
class CategoryLinkRepository implements \Magento\Catalog\Api\CategoryLinkRepositoryInterface
18+
/**
19+
* @inheritdoc
20+
*/
21+
class CategoryLinkRepository implements CategoryLinkRepositoryInterface, CategoryListDeleteBySkuInterface
1322
{
1423
/**
1524
* @var CategoryRepository
1625
*/
1726
protected $categoryRepository;
1827

1928
/**
20-
* @var \Magento\Catalog\Api\ProductRepositoryInterface
29+
* @var ProductRepositoryInterface
2130
*/
2231
protected $productRepository;
2332

2433
/**
25-
* @param \Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository
26-
* @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
34+
* @var Product
35+
*/
36+
private $productResource;
37+
38+
/**
39+
* @param CategoryRepositoryInterface $categoryRepository
40+
* @param ProductRepositoryInterface $productRepository
41+
* @param Product $productResource
2742
*/
2843
public function __construct(
29-
\Magento\Catalog\Api\CategoryRepositoryInterface $categoryRepository,
30-
\Magento\Catalog\Api\ProductRepositoryInterface $productRepository
44+
CategoryRepositoryInterface $categoryRepository,
45+
ProductRepositoryInterface $productRepository,
46+
Product $productResource = null
3147
) {
3248
$this->categoryRepository = $categoryRepository;
3349
$this->productRepository = $productRepository;
50+
$this->productResource = $productResource ?? ObjectManager::getInstance()->get(Product::class);
3451
}
3552

3653
/**
37-
* {@inheritdoc}
54+
* @inheritdoc
3855
*/
3956
public function save(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $productLink)
4057
{
@@ -60,15 +77,15 @@ public function save(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $pro
6077
}
6178

6279
/**
63-
* {@inheritdoc}
80+
* @inheritdoc
6481
*/
6582
public function delete(\Magento\Catalog\Api\Data\CategoryProductLinkInterface $productLink)
6683
{
6784
return $this->deleteByIds($productLink->getCategoryId(), $productLink->getSku());
6885
}
6986

7087
/**
71-
* {@inheritdoc}
88+
* @inheritdoc
7289
*/
7390
public function deleteByIds($categoryId, $sku)
7491
{
@@ -101,4 +118,44 @@ public function deleteByIds($categoryId, $sku)
101118
}
102119
return true;
103120
}
121+
122+
/**
123+
* @inheritdoc
124+
*/
125+
public function deleteBySkus(int $categoryId, array $productSkuList): bool
126+
{
127+
$category = $this->categoryRepository->get($categoryId);
128+
$products = $this->productResource->getProductsIdsBySkus($productSkuList);
129+
130+
if (!$products) {
131+
throw new InputException(__("The category doesn't contain the specified products."));
132+
}
133+
134+
$productPositions = $category->getProductsPosition();
135+
136+
foreach ($products as $productId) {
137+
if (isset($productPositions[$productId])) {
138+
unset($productPositions[$productId]);
139+
}
140+
}
141+
142+
$category->setPostedProducts($productPositions);
143+
144+
try {
145+
$category->save();
146+
} catch (\Exception $e) {
147+
throw new CouldNotSaveException(
148+
__(
149+
'Could not save products "%products" to category %category',
150+
[
151+
"products" => implode(',', $productSkuList),
152+
"category" => $category->getId()
153+
]
154+
),
155+
$e
156+
);
157+
}
158+
159+
return true;
160+
}
104161
}

0 commit comments

Comments
 (0)