Skip to content

Commit 2f25761

Browse files
authored
Merge branch '2.4-develop' into 2.3-develop
2 parents bf87df0 + 1ad65a3 commit 2f25761

File tree

1,856 files changed

+63741
-10518
lines changed

Some content is hidden

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

1,856 files changed

+63741
-10518
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
[![Open Source Helpers](https://www.codetriage.com/magento/magento2/badges/users.svg)](https://www.codetriage.com/magento/magento2)
22
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magento/magento2?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
33
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/magento-2/localized.svg)](https://crowdin.com/project/magento-2)
4-
<h2>Welcome</h2>
4+
5+
## Welcome
56
Welcome to Magento 2 installation! We're glad you chose to install Magento 2, a cutting-edge, feature-rich eCommerce solution that gets results.
67

78
## Magento System Requirements
@@ -30,7 +31,7 @@ To suggest documentation improvements, click [here][4].
3031
[4]: https://devdocs.magento.com
3132

3233
<h3>Community Maintainers</h3>
33-
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.
34+
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 to these Community Maintainers for their valuable contributions.
3435

3536
<a href="https://magento.com/magento-contributors#maintainers">
3637
<img src="https://raw.githubusercontent.com/wiki/magento/magento2/images/maintainers.png"/>

app/code/Magento/AdminAnalytics/view/adminhtml/ui_component/admin_usage_notification.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@
8585
<item name="text" xsi:type="string" translate="true"><![CDATA[
8686
<p>Help us improve Magento Admin by allowing us to collect usage data.</p>
8787
<p>All usage data that we collect for this purpose cannot be used to individually identify you and is used only to improve the Magento Admin and related products and services.</p>
88-
<p>You can learn more and opt out at any time by following the instructions in <a href="https://docs.magento.com/m2/ce/user_guide/stores/admin.html" target="_blank">merchant documentation</a>.</p>
88+
<p>You can learn more and opt out at any time by following the instructions in <a href="https://docs.magento.com/m2/ce/user_guide/stores/admin.html" target="_blank" tabindex="0">merchant documentation</a>.</p>
8989
]]></item>
9090
</item>
9191
</argument>

app/code/Magento/AdminAnalytics/view/adminhtml/web/js/modal/component.js

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,7 @@ define([
2020
enableLogAction: '${ $.provider }:data.enableLogAction',
2121
disableLogAction: '${ $.provider }:data.disableLogAction'
2222
},
23-
options: {
24-
keyEventHandlers: {
25-
/**
26-
* Prevents escape key from exiting out of modal
27-
*/
28-
escapeKey: function () {
29-
return;
30-
}
31-
}
32-
},
23+
options: {},
3324
notificationWindow: null
3425
},
3526

@@ -41,11 +32,32 @@ define([
4132
this._super();
4233
},
4334

35+
/**
36+
* Configure ESC and TAB so user can't leave modal
37+
* without selecting an option
38+
*
39+
* @returns {Object} Chainable.
40+
*/
41+
initModalEvents: function () {
42+
this._super();
43+
//Don't allow ESC key to close modal
44+
this.options.keyEventHandlers.escapeKey = this.handleEscKey.bind(this);
45+
//Restrict tab action to the modal
46+
this.options.keyEventHandlers.tabKey = this.handleTabKey.bind(this);
47+
48+
return this;
49+
},
50+
4451
/**
4552
* Once the modal is opened it hides the X
4653
*/
4754
onOpened: function () {
48-
$('.modal-header button.action-close').hide();
55+
$('.modal-header button.action-close').attr('disabled', true).hide();
56+
57+
this.focusableElements = $(this.rootSelector).find('a[href], button:enabled');
58+
this.firstFocusableElement = this.focusableElements[0];
59+
this.lastFocusableElement = this.focusableElements[this.focusableElements.length - 1];
60+
this.firstFocusableElement.focus();
4961
},
5062

5163
/**
@@ -104,11 +116,70 @@ define([
104116
* Allows admin usage popup to be shown first and then new release notification
105117
*/
106118
openReleasePopup: function () {
107-
var notifiModal = registry.get('release_notification.release_notification.notification_modal_1');
119+
var notificationModalSelector = 'release_notification.release_notification.notification_modal_1';
108120

109121
if (analyticsPopupConfig.releaseVisible) {
110-
notifiModal.initializeContentAfterAnalytics();
122+
registry.get(notificationModalSelector).initializeContentAfterAnalytics();
111123
}
124+
},
125+
126+
/**
127+
* Handle Tab and Shift+Tab key event
128+
*
129+
* Keep the tab actions restricted to the popup modal
130+
* so the user must select an option to dismiss the modal
131+
*/
132+
handleTabKey: function (event) {
133+
var modal = this,
134+
KEY_TAB = 9;
135+
136+
/**
137+
* Handle Shift+Tab to tab backwards
138+
*/
139+
function handleBackwardTab() {
140+
if (document.activeElement === modal.firstFocusableElement ||
141+
document.activeElement === $(modal.rootSelector)[0]
142+
) {
143+
event.preventDefault();
144+
modal.lastFocusableElement.focus();
145+
}
146+
}
147+
148+
/**
149+
* Handle Tab forward
150+
*/
151+
function handleForwardTab() {
152+
if (document.activeElement === modal.lastFocusableElement) {
153+
event.preventDefault();
154+
modal.firstFocusableElement.focus();
155+
}
156+
}
157+
158+
switch (event.keyCode) {
159+
case KEY_TAB:
160+
if (modal.focusableElements.length === 1) {
161+
event.preventDefault();
162+
break;
163+
}
164+
165+
if (event.shiftKey) {
166+
handleBackwardTab();
167+
break;
168+
}
169+
handleForwardTab();
170+
break;
171+
default:
172+
break;
173+
}
174+
},
175+
176+
/**
177+
* Handle Esc key
178+
*
179+
* Esc key should not close modal
180+
*/
181+
handleEscKey: function (event) {
182+
event.preventDefault();
112183
}
113184
}
114185
);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AdminSystemMessagesWarningActionGroup">
12+
<annotations>
13+
<description>Check warning system message exists.</description>
14+
</annotations>
15+
<arguments>
16+
<argument name="message" type="string"/>
17+
</arguments>
18+
19+
<waitForElementVisible selector="{{AdminSystemMessagesSection.systemMessagesDropdown}}" stepKey="waitMessagesDropdownAppears"/>
20+
<conditionalClick selector="{{AdminSystemMessagesSection.systemMessagesDropdown}}" dependentSelector="{{AdminSystemMessagesSection.messagesBlock}}" visible="false" stepKey="openMessagesBlockIfCollapsed"/>
21+
<see userInput="{{message}}" selector="{{AdminSystemMessagesSection.warning}}" stepKey="seeWarningMessage"/>
22+
</actionGroup>
23+
</actionGroups>

app/code/Magento/AdminNotification/Test/Mftf/Section/AdminSystemMessagesSection.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,9 @@
1111
<section name="AdminSystemMessagesSection">
1212
<element name="systemMessagesDropdown" type="button" selector="#system_messages .message-system-action-dropdown"/>
1313
<element name="actionMessageLog" type="button" selector="//*[contains(@class, 'message-system-summary')]/a[contains(text(), '{{textMessage}}')]" parameterized="true"/>
14+
<element name="messagesBlock" type="block" selector="#system_messages div.message-system-collapsible"/>
15+
<element name="success" type="text" selector="#system_messages div.message-success"/>
16+
<element name="warning" type="text" selector="#system_messages div.message-warning"/>
17+
<element name="notice" type="text" selector="#system_messages div.message-notice"/>
1418
</section>
1519
</sections>
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\AdvancedSearch\Test\Unit\Model\Recommendations;
9+
10+
use Magento\AdvancedSearch\Model\Recommendations\DataProvider;
11+
use Magento\Framework\App\Config\ScopeConfigInterface;
12+
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
13+
use Magento\Catalog\Model\Layer\Resolver;
14+
use Magento\AdvancedSearch\Model\ResourceModel\Recommendations;
15+
use Magento\AdvancedSearch\Model\ResourceModel\RecommendationsFactory;
16+
use Magento\Search\Model\QueryResult;
17+
use Magento\Search\Model\QueryResultFactory;
18+
use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection;
19+
use Magento\Catalog\Model\Layer as SearchLayer;
20+
use Magento\Store\Model\ScopeInterface;
21+
use Magento\Search\Model\QueryInterface;
22+
23+
/**
24+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
25+
*
26+
* Class \Magento\AdvancedSearch\Test\Unit\Model\Recommendations\DataProviderTest
27+
*/
28+
class DataProviderTest extends \PHPUnit\Framework\TestCase
29+
{
30+
/**
31+
* @var DataProvider;
32+
*/
33+
private $model;
34+
35+
/**
36+
* @var ObjectManagerHelper
37+
*/
38+
private $objectManagerHelper;
39+
40+
/**
41+
* @var \PHPUnit_Framework_MockObject_MockObject|ScopeConfigInterface
42+
*/
43+
private $scopeConfigMock;
44+
45+
/**
46+
* @var \PHPUnit_Framework_MockObject_MockObject|Resolver
47+
*/
48+
private $layerResolverMock;
49+
50+
/**
51+
* @var \PHPUnit_Framework_MockObject_MockObject|SearchLayer
52+
*/
53+
private $searchLayerMock;
54+
55+
/**
56+
* @var \PHPUnit_Framework_MockObject_MockObject|RecommendationsFactory
57+
*/
58+
private $recommendationsFactoryMock;
59+
60+
/**
61+
* @var \PHPUnit_Framework_MockObject_MockObject|Recommendations
62+
*/
63+
private $recommendationsMock;
64+
65+
/**
66+
* @var \PHPUnit_Framework_MockObject_MockObject|Resolver
67+
*/
68+
private $queryResultFactory;
69+
70+
/**
71+
* Set up test environment.
72+
*
73+
* @return void
74+
*/
75+
protected function setUp()
76+
{
77+
$this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class);
78+
$this->layerResolverMock = $this->getMockBuilder(Resolver::class)
79+
->disableOriginalConstructor()
80+
->setMethods(['get'])
81+
->getMock();
82+
83+
$this->searchLayerMock = $this->createMock(SearchLayer::class);
84+
85+
$this->layerResolverMock->expects($this->any())
86+
->method('get')
87+
->will($this->returnValue($this->searchLayerMock));
88+
89+
$this->recommendationsFactoryMock = $this->getMockBuilder(RecommendationsFactory::class)
90+
->disableOriginalConstructor()
91+
->setMethods(['create'])
92+
->getMock();
93+
94+
$this->recommendationsMock = $this->createMock(Recommendations::class);
95+
96+
$this->queryResultFactory = $this->getMockBuilder(QueryResultFactory::class)
97+
->disableOriginalConstructor()
98+
->setMethods(['create'])
99+
->getMock();
100+
101+
$this->objectManagerHelper = new ObjectManagerHelper($this);
102+
$this->model = $this->objectManagerHelper->getObject(
103+
DataProvider::class,
104+
[
105+
'scopeConfig' => $this->scopeConfigMock,
106+
'layerResolver' => $this->layerResolverMock,
107+
'recommendationsFactory' => $this->recommendationsFactoryMock,
108+
'queryResultFactory' => $this->queryResultFactory
109+
]
110+
);
111+
}
112+
113+
/**
114+
* Test testGetItems() when Search Recommendations disabled.
115+
*
116+
* @return void
117+
*/
118+
public function testGetItemsWhenDisabledSearchRecommendations()
119+
{
120+
$isEnabledSearchRecommendations = false;
121+
122+
/** @var $queryInterfaceMock QueryInterface */
123+
$queryInterfaceMock = $this->createMock(QueryInterface::class);
124+
125+
$this->scopeConfigMock->expects($this->any())
126+
->method('isSetFlag')
127+
->with('catalog/search/search_recommendations_enabled', ScopeInterface::SCOPE_STORE)
128+
->willReturn($isEnabledSearchRecommendations);
129+
130+
$result = $this->model->getItems($queryInterfaceMock);
131+
$this->assertEquals([], $result);
132+
}
133+
134+
/**
135+
* Test testGetItems() when Search Recommendations enabled.
136+
*
137+
* @return void
138+
*/
139+
public function testGetItemsWhenEnabledSearchRecommendations()
140+
{
141+
$storeId = 1;
142+
$searchRecommendationsCountConfig = 2;
143+
$isEnabledSearchRecommendations = true;
144+
$queryText = 'test';
145+
146+
/** @var $queryInterfaceMock QueryInterface */
147+
$queryInterfaceMock = $this->createMock(QueryInterface::class);
148+
$queryInterfaceMock->expects($this->any())->method('getQueryText')->willReturn($queryText);
149+
150+
$this->scopeConfigMock->expects($this->any())
151+
->method('isSetFlag')
152+
->with('catalog/search/search_recommendations_enabled', ScopeInterface::SCOPE_STORE)
153+
->willReturn($isEnabledSearchRecommendations);
154+
155+
$this->scopeConfigMock->expects($this->any())
156+
->method('getValue')
157+
->with('catalog/search/search_recommendations_count', ScopeInterface::SCOPE_STORE)
158+
->willReturn($searchRecommendationsCountConfig);
159+
160+
$productCollectionMock = $this->createMock(ProductCollection::class);
161+
$productCollectionMock->expects($this->any())->method('getStoreId')->willReturn($storeId);
162+
163+
$this->searchLayerMock->expects($this->any())->method('getProductCollection')
164+
->willReturn($productCollectionMock);
165+
166+
$this->recommendationsFactoryMock->expects($this->any())->method('create')
167+
->willReturn($this->recommendationsMock);
168+
169+
$this->recommendationsMock->expects($this->any())->method('getRecommendationsByQuery')
170+
->with($queryText, ['store_id' => $storeId], $searchRecommendationsCountConfig)
171+
->willReturn(
172+
[
173+
[
174+
'query_text' => 'a',
175+
'num_results' => 3
176+
],
177+
[
178+
'query_text' => 'b',
179+
'num_results' => 2
180+
]
181+
]
182+
);
183+
$queryResultMock = $this->createMock(QueryResult::class);
184+
$this->queryResultFactory->expects($this->any())->method('create')->willReturn($queryResultMock);
185+
186+
$result = $this->model->getItems($queryInterfaceMock);
187+
$this->assertEquals(2, count($result));
188+
}
189+
}

app/code/Magento/Analytics/Model/ExportDataHandler.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function __construct(
8989
public function prepareExportData()
9090
{
9191
try {
92-
$tmpDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
92+
$tmpDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::SYS_TMP);
9393

9494
$this->prepareDirectory($tmpDirectory, $this->getTmpFilesDirRelativePath());
9595
$this->reportWriter->write($tmpDirectory, $this->getTmpFilesDirRelativePath());

app/code/Magento/Analytics/Test/Mftf/Data/UserRoleData.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<entities xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1010
xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
1111
<entity name="adminNoReportRole" type="user_role">
12+
<data key="all">0</data>
1213
<data key="rolename" unique="suffix">noreport</data>
1314
<data key="current_password">123123q</data>
1415
<array key="resource">

0 commit comments

Comments
 (0)