diff --git a/app/code/Magento/AdminAnalytics/Test/Mftf/Test/AdminCheckAnalyticsTrackingTest.xml b/app/code/Magento/AdminAnalytics/Test/Mftf/Test/AdminCheckAnalyticsTrackingTest.xml new file mode 100644 index 0000000000000..4f0e9bb000a27 --- /dev/null +++ b/app/code/Magento/AdminAnalytics/Test/Mftf/Test/AdminCheckAnalyticsTrackingTest.xml @@ -0,0 +1,35 @@ + + + + + + + + + <description value="AdminAnalytics Check Tracking."/> + <severity value="MINOR"/> + <testCaseId value="MC-36869"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> + <magentoCLI command="config:set admin/usage/enabled 1" stepKey="enableAdminUsageTracking"/> + <actionGroup ref="CliCacheCleanActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="config full_page"/> + </actionGroup> + <reloadPage stepKey="pageReload"/> + </before> + <after> + <magentoCLI command="config:set admin/usage/enabled 0" stepKey="disableAdminUsageTracking"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <waitForPageLoad stepKey="waitForPageReloaded"/> + <seeInPageSource html="var adminAnalyticsMetadata =" stepKey="seeInPageSource"/> + </test> +</tests> diff --git a/app/code/Magento/AdminAnalytics/etc/csp_whitelist.xml b/app/code/Magento/AdminAnalytics/etc/csp_whitelist.xml index e937a3e18148a..f16a66aa090e3 100644 --- a/app/code/Magento/AdminAnalytics/etc/csp_whitelist.xml +++ b/app/code/Magento/AdminAnalytics/etc/csp_whitelist.xml @@ -13,5 +13,24 @@ <value id="adobedtm" type="host">assets.adobedtm.com</value> </values> </policy> + <policy id="img-src"> + <values> + <value id="adobedtm" type="host">assets.adobedtm.com</value> + <value id="omtrdc" type="host">amcglobal.sc.omtrdc.net</value> + <value id="dpmdemdex" type="host">dpm.demdex.net</value> + <value id="everesttech" type="host">cm.everesttech.net</value> + </values> + </policy> + <policy id="connect-src"> + <values> + <value id="dpmdemdex" type="host">dpm.demdex.net</value> + <value id="omtrdc" type="host">amcglobal.sc.omtrdc.net</value> + </values> + </policy> + <policy id="frame-src"> + <values> + <value id="amcdemdex" type="host">fast.amc.demdex.net</value> + </values> + </policy> </policies> </csp_whitelist> diff --git a/app/code/Magento/AdminNotification/Test/Mftf/Section/AdminNotificationToolbarSection.xml b/app/code/Magento/AdminNotification/Test/Mftf/Section/AdminNotificationToolbarSection.xml new file mode 100644 index 0000000000000..c4a9290cb5641 --- /dev/null +++ b/app/code/Magento/AdminNotification/Test/Mftf/Section/AdminNotificationToolbarSection.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminNotificationToolbarSection"> + <element name="notification" type="block" selector=".notifications-wrapper.admin__action-dropdown-wrap"/> + <element name="notificationCounter" type="block" selector=".notifications-action.admin__action-dropdown .notifications-counter"/> + </section> +</sections> diff --git a/app/code/Magento/AdminNotification/Test/Mftf/Test/AdminSystemNotificationToolbarBlockAclTest.xml b/app/code/Magento/AdminNotification/Test/Mftf/Test/AdminSystemNotificationToolbarBlockAclTest.xml new file mode 100644 index 0000000000000..1ab277b4f788a --- /dev/null +++ b/app/code/Magento/AdminNotification/Test/Mftf/Test/AdminSystemNotificationToolbarBlockAclTest.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminSystemNotificationToolbarBlockAclTest"> + <annotations> + <features value="AdminNotification"/> + <stories value="Acl notification toolbar"/> + <title value="Admin system notification toolbar block acl test"/> + <description value="Admin should not see system notification toolbar block if acl not restricted"/> + <severity value="MAJOR"/> + <testCaseId value="MC-36011"/> + <group value="menu"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> + + <actionGroup ref="AdminFillUserRoleRequiredDataActionGroup" stepKey="fillUserRoleRequiredData"> + <argument name="User" value="adminRole"/> + <argument name="restrictedRole" value="Stores"/> + </actionGroup> + <actionGroup ref="AdminUserClickRoleResourceTabActionGroup" stepKey="goToRoleResourcesTab" /> + <actionGroup ref="AdminAddRestrictedRoleActionGroup" stepKey="addRestrictedRoleStores"> + <argument name="User" value="adminRole"/> + <argument name="restrictedRole" value="Products"/> + </actionGroup> + <actionGroup ref="AdminUserSaveRoleActionGroup" stepKey="saveUserRole" /> + + <actionGroup ref="AdminCreateUserActionGroup" stepKey="createAdminUser"> + <argument name="role" value="adminRole"/> + <argument name="User" value="admin2"/> + </actionGroup> + + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> + </before> + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutAsSaleRoleUser"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <!--Delete created data--> + <actionGroup ref="AdminUserOpenAdminRolesPageActionGroup" stepKey="navigateToUserRoleGrid"/> + <actionGroup ref="AdminDeleteRoleActionGroup" stepKey="deleteUserRole"> + <argument name="role" value="adminRole"/> + </actionGroup> + <actionGroup ref="AdminOpenAdminUsersPageActionGroup" stepKey="goToAllUsersPage"/> + <actionGroup ref="AdminDeleteNewUserActionGroup" stepKey="deleteUser"> + <argument name="userName" value="{{admin2.username}}"/> + </actionGroup> + </after> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsNewUser"> + <argument name="username" value="{{admin2.username}}"/> + <argument name="password" value="{{admin2.password}}"/> + </actionGroup> + + <waitForPageLoad stepKey="waitBeforePageLoad"/> + <dontSeeElement selector="{{AdminNotificationToolbarSection.notification}}" stepKey="doNotSeeNotificationBellIcon"/> + </test> +</tests> diff --git a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml index eed6b53f34315..b71fbd40cadb7 100644 --- a/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml +++ b/app/code/Magento/AdminNotification/view/adminhtml/layout/default.xml @@ -20,7 +20,11 @@ template="Magento_AdminNotification::notification/window.phtml"/> </referenceContainer> <referenceContainer name="header"> - <block class="Magento\AdminNotification\Block\ToolbarEntry" name="notification.messages" before="user" template="Magento_AdminNotification::toolbar_entry.phtml"/> + <block class="Magento\AdminNotification\Block\ToolbarEntry" + name="notification.messages" + before="user" + aclResource="Magento_AdminNotification::show_toolbar" + template="Magento_AdminNotification::toolbar_entry.phtml"/> </referenceContainer> </body> </page> diff --git a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php index 974397226c56c..254dbcca852ee 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php +++ b/app/code/Magento/AdvancedPricingImportExport/Model/Import/AdvancedPricing.php @@ -10,7 +10,7 @@ use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; /** - * Class AdvancedPricing + * Import advanced pricing class * * @SuppressWarnings(PHPMD.ExcessiveParameterList) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -19,35 +19,20 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity { const VALUE_ALL_GROUPS = 'ALL GROUPS'; - const VALUE_ALL_WEBSITES = 'All Websites'; - const COL_SKU = 'sku'; - const COL_TIER_PRICE_WEBSITE = 'tier_price_website'; - const COL_TIER_PRICE_CUSTOMER_GROUP = 'tier_price_customer_group'; - const COL_TIER_PRICE_QTY = 'tier_price_qty'; - const COL_TIER_PRICE = 'tier_price'; - const COL_TIER_PRICE_PERCENTAGE_VALUE = 'percentage_value'; - const COL_TIER_PRICE_TYPE = 'tier_price_value_type'; - const TIER_PRICE_TYPE_FIXED = 'Fixed'; - const TIER_PRICE_TYPE_PERCENT = 'Discount'; - const TABLE_TIER_PRICE = 'catalog_product_entity_tier_price'; - const DEFAULT_ALL_GROUPS_GROUPED_PRICE_VALUE = '0'; - const ENTITY_TYPE_CODE = 'advanced_pricing'; - const VALIDATOR_MAIN = 'validator'; - const VALIDATOR_WEBSITE = 'validator_website'; /** @@ -55,7 +40,6 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract * @see VALIDATOR_TIER_PRICE */ private const VALIDATOR_TEAR_PRICE = 'validator_tier_price'; - private const VALIDATOR_TIER_PRICE = 'validator_tier_price'; /** @@ -176,10 +160,8 @@ class AdvancedPricing extends \Magento\ImportExport\Model\Import\Entity\Abstract * @param \Magento\Framework\Json\Helper\Data $jsonHelper * @param \Magento\ImportExport\Helper\Data $importExportData * @param \Magento\ImportExport\Model\ResourceModel\Import\Data $importData - * @param \Magento\Eav\Model\Config $config * @param \Magento\Framework\App\ResourceConnection $resource * @param \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper - * @param \Magento\Framework\Stdlib\StringUtils $string * @param ProcessingErrorAggregatorInterface $errorAggregator * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime * @param \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory $resourceFactory @@ -197,10 +179,8 @@ public function __construct( \Magento\Framework\Json\Helper\Data $jsonHelper, \Magento\ImportExport\Helper\Data $importExportData, \Magento\ImportExport\Model\ResourceModel\Import\Data $importData, - \Magento\Eav\Model\Config $config, \Magento\Framework\App\ResourceConnection $resource, \Magento\ImportExport\Model\ResourceModel\Helper $resourceHelper, - \Magento\Framework\Stdlib\StringUtils $string, ProcessingErrorAggregatorInterface $errorAggregator, \Magento\Framework\Stdlib\DateTime\DateTime $dateTime, \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory $resourceFactory, diff --git a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php index e57ed2c91409d..08d75f0f36f07 100644 --- a/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php +++ b/app/code/Magento/AdvancedPricingImportExport/Test/Unit/Model/Import/AdvancedPricingTest.php @@ -16,7 +16,6 @@ use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as RowValidatorInterface; use Magento\CatalogImportExport\Model\Import\Product\StoreResolver; use Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory as ResourceFactory; -use Magento\Eav\Model\Config; use Magento\Eav\Model\Entity\Type; use Magento\Framework\App\ResourceConnection; use Magento\Framework\DB\Adapter\AdapterInterface; @@ -26,7 +25,6 @@ use Magento\Framework\Json\Helper\Data; use Magento\Framework\Stdlib\DateTime\DateTime; use Magento\Framework\Stdlib\DateTime\TimezoneInterface; -use Magento\Framework\Stdlib\StringUtils; use Magento\ImportExport\Model\Import; use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface; use Magento\ImportExport\Model\ResourceModel\Helper; @@ -99,11 +97,6 @@ class AdvancedPricingTest extends AbstractImportTestCase */ protected $dataSourceModel; - /** - * @var Config - */ - protected $eavConfig; - /** * @var TimezoneInterface|MockObject */ @@ -139,11 +132,6 @@ class AdvancedPricingTest extends AbstractImportTestCase */ protected $advancedPricing; - /** - * @var StringUtils - */ - protected $stringObject; - /** * @var ProcessingErrorAggregatorInterface */ @@ -165,10 +153,8 @@ protected function setUp(): void ); $this->resource->method('getConnection')->willReturn($this->connection); $this->dataSourceModel = $this->createMock(\Magento\ImportExport\Model\ResourceModel\Import\Data::class); - $this->eavConfig = $this->createMock(Config::class); $entityType = $this->createMock(Type::class); $entityType->method('getEntityTypeId')->willReturn(''); - $this->eavConfig->method('getEntityType')->willReturn($entityType); $this->resourceFactory = $this->getMockBuilder( \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModelFactory::class ) @@ -193,7 +179,6 @@ protected function setUp(): void $this->tierPriceValidator = $this->createMock( TierPrice::class ); - $this->stringObject = $this->createMock(StringUtils::class); $this->errorAggregator = $this->getErrorAggregatorObject(); $this->dateTime = $this->getMockBuilder(DateTime::class) ->disableOriginalConstructor() @@ -1070,10 +1055,8 @@ private function getAdvancedPricingMock($methods = []) $this->jsonHelper, $this->importExportData, $this->dataSourceModel, - $this->eavConfig, $this->resource, $this->resourceHelper, - $this->stringObject, $this->errorAggregator, $this->dateTime, $this->resourceFactory, diff --git a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminOpenConfigGeneralAnalyticsPageActionGroup.xml b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminOpenConfigGeneralAnalyticsPageActionGroup.xml new file mode 100644 index 0000000000000..bfa3e436e09d0 --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AdminOpenConfigGeneralAnalyticsPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenConfigGeneralAnalyticsPageActionGroup"> + <annotations> + <description>Open Config General Analytics Page.</description> + </annotations> + + <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <waitForPageLoad stepKey="waitPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml new file mode 100644 index 0000000000000..51d77228c8dcf --- /dev/null +++ b/app/code/Magento/Analytics/Test/Mftf/ActionGroup/AssertAdminAdvancedReportingPageUrlActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminAdvancedReportingPageUrlActionGroup"> + <annotations> + <description>Assert admin advanced reporting page url.</description> + </annotations> + + <switchToNextTab stepKey="switchToNewTab"/> + <waitForPageLoad stepKey="waitForAdvancedReportingPageLoad"/> + <seeInCurrentUrl url="advancedreporting.rjmetrics.com/report" stepKey="seeAssertAdvancedReportingPageUrl"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml index cbcbb3a5dd64c..9c99041be0df6 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingButtonTest.xml @@ -32,7 +32,6 @@ <amOnPage url="{{AdminDashboardPage.url}}" stepKey="amOnDashboardPage"/> <waitForPageLoad stepKey="waitForDashboardPageLoad"/> <click selector="{{AdminAdvancedReportingSection.goToAdvancedReporting}}" stepKey="clickGoToAdvancedReporting"/> - <switchToNextTab stepKey="switchToNewTab"/> - <seeInCurrentUrl url="advancedreporting.rjmetrics.com/report" stepKey="seeAssertAdvancedReportingPageUrl"/> + <actionGroup ref="AssertAdminAdvancedReportingPageUrlActionGroup" stepKey="assertAdvancedReportingPageUrl"/> </test> </tests> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml index ee25e80fcab30..f350452cfc7d0 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminAdvancedReportingNavigateMenuTest.xml @@ -29,8 +29,6 @@ <argument name="menuUiId" value="{{AdminMenuReports.dataUiId}}"/> <argument name="submenuUiId" value="{{AdminMenuReportsBusinessIntelligenceAdvancedReporting.dataUiId}}"/> </actionGroup> - <switchToNextTab stepKey="switchToNewTab"/> - <waitForPageLoad stepKey="waitForAdvancedReportingPageLoad"/> - <seeInCurrentUrl url="advancedreporting.rjmetrics.com/report" stepKey="seeAssertAdvancedReportingPageUrl"/> + <actionGroup ref="AssertAdminAdvancedReportingPageUrlActionGroup" stepKey="assertAdvancedReportingPageUrl"/> </test> </tests> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml index 17d463030d91c..a5b01a9221350 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationBlankIndustryTest.xml @@ -17,11 +17,13 @@ <testCaseId value="MAGETWO-63981"/> <group value="analytics"/> </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminOpenConfigGeneralAnalyticsPageActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="--Please Select--" stepKey="selectAdvancedReportingIndustry"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml index b03488c240604..6116dd72528f7 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationEnableDisableAnalyticsTest.xml @@ -17,11 +17,13 @@ <testCaseId value="MAGETWO-66465"/> <group value="analytics"/> </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminOpenConfigGeneralAnalyticsPageActionGroup" stepKey="amOnAdminConfig"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingServiceLabel}}" userInput="Advanced Reporting Service" stepKey="seeAdvancedReportingServiceLabelEnabled"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml index c19fddc6aa0ce..1a77c365c8098 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationIndustryTest.xml @@ -18,9 +18,13 @@ <testCaseId value="MAGETWO-63898"/> <group value="analytics"/> </annotations> - - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> + </after> + <actionGroup ref="AdminOpenConfigGeneralAnalyticsPageActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <see selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustryLabel}}" userInput="Industry" stepKey="seeAdvancedReportingIndustryLabel"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="Apps and Games" stepKey="selectAdvancedReportingIndustry"/> diff --git a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml index 6231b17c17b02..60585e73baeaa 100644 --- a/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml +++ b/app/code/Magento/Analytics/Test/Mftf/Test/AdminConfigurationTimeToSendDataTest.xml @@ -18,11 +18,13 @@ <testCaseId value="MAGETWO-66464"/> <group value="analytics"/> </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> <after> <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <amOnPage url="{{AdminConfigGeneralAnalyticsPage.url}}" stepKey="amOnAdminConfig"/> + <actionGroup ref="AdminOpenConfigGeneralAnalyticsPageActionGroup" stepKey="amOnAdminConfig"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingService}}" userInput="Enable" stepKey="selectAdvancedReportingServiceEnabled"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingIndustry}}" userInput="Apps and Games" stepKey="selectAdvancedReportingIndustry"/> <selectOption selector="{{AdminConfigAdvancedReportingSection.advancedReportingHour}}" userInput="23" stepKey="selectAdvancedReportingHour"/> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToEmailToFriendSettingsActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToEmailToFriendSettingsActionGroup.xml new file mode 100644 index 0000000000000..05903581747d9 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminNavigateToEmailToFriendSettingsActionGroup.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminNavigateToEmailToFriendSettingsActionGroup"> + <amOnPage url="{{AdminConfigurationEmailToFriendPage.url}}" stepKey="navigateToPersistencePage"/> + <conditionalClick selector="{{AdminEmailToFriendSection.DefaultLayoutsTab}}" dependentSelector="{{AdminEmailToFriendSection.CheckIfTabExpand}}" visible="true" stepKey="clickTab"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenGeneralConfigurationPageActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenGeneralConfigurationPageActionGroup.xml new file mode 100644 index 0000000000000..ecacf063938ad --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenGeneralConfigurationPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenGeneralConfigurationPageActionGroup"> + <annotations> + <description>Open general configuration page.</description> + </annotations> + + <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="openGeneralConfigurationPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenWebConfigurationPageActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenWebConfigurationPageActionGroup.xml new file mode 100644 index 0000000000000..e640eda7d653d --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AdminOpenWebConfigurationPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenWebConfigurationPageActionGroup"> + <annotations> + <description>Open web configuration page.</description> + </annotations> + + <amOnPage url="{{WebConfigurationPage.url}}" stepKey="openWebConfigurationPage"/> + <waitForPageLoad stepKey="waitPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminEmailToFriendOptionsAvailableActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminEmailToFriendOptionsAvailableActionGroup.xml new file mode 100644 index 0000000000000..88152a2cb4f73 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminEmailToFriendOptionsAvailableActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminEmailToFriendOptionsAvailableActionGroup"> + <seeElement stepKey="seeEmailTemplateInput" selector="{{AdminEmailToFriendSection.emailTemplate}}"/> + <seeElement stepKey="seeAllowForGuestsInput" selector="{{AdminEmailToFriendSection.allowForGuests}}"/> + <seeElement stepKey="seeMaxRecipientsInput" selector="{{AdminEmailToFriendSection.maxRecipients}}"/> + <seeElement stepKey="seeMaxPerHourInput" selector="{{AdminEmailToFriendSection.maxPerHour}}"/> + <seeElement stepKey="seeLimitSendingBy" selector="{{AdminEmailToFriendSection.limitSendingBy}}"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPageIs404ActionGroup.xml b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPageIs404ActionGroup.xml new file mode 100644 index 0000000000000..09b0bdcc146ae --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/ActionGroup/AssertAdminPageIs404ActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminPageIs404ActionGroup"> + <annotations> + <description>Validates that the '404 Error' message is present in the current Admin Page Header.</description> + </annotations> + + <see userInput="404 Error" selector="{{AdminHeaderSection.pageHeading}}" stepKey="see404PageHeading"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationEmailToFriendPage.xml b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationEmailToFriendPage.xml new file mode 100644 index 0000000000000..14bd514f1a16f --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Page/AdminConfigurationEmailToFriendPage.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> + <page name="AdminConfigurationEmailToFriendPage" url="admin/system_config/edit/section/sendfriend/" module="Catalog" area="admin"> + <section name="AdminEmailToFriendSection"/> + </page> +</pages> diff --git a/app/code/Magento/Backend/Test/Mftf/Test/AdminCatalogEmailToFriendSettingsTest.xml b/app/code/Magento/Backend/Test/Mftf/Test/AdminCatalogEmailToFriendSettingsTest.xml new file mode 100644 index 0000000000000..b410a4cb73de7 --- /dev/null +++ b/app/code/Magento/Backend/Test/Mftf/Test/AdminCatalogEmailToFriendSettingsTest.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCatalogEmailToFriendSettingsTest"> + <annotations> + <features value="Backend"/> + <stories value="Enable Email To A Friend Functionality"/> + <title value="Admin should be able to manage settings of Email To A Friend Functionality"/> + <description value="Admin should be able to enable Email To A Friend functionality in Magento Admin backend and see additional options"/> + <group value="backend"/> + <severity value="MINOR"></severity> + <testCaseId value="MC-35895"/> + </annotations> + + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <magentoCLI stepKey="enableSendFriend" command="config:set sendfriend/email/enabled 1"/> + <magentoCLI stepKey="cacheClean" command="cache:clean config"/> + </before> + <after> + <magentoCLI stepKey="disableSendFriend" command="config:set sendfriend/email/enabled 0"/> + <magentoCLI stepKey="cacheClean" command="cache:clean config"/> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + + <actionGroup ref="AdminNavigateToEmailToFriendSettingsActionGroup" stepKey="navigateToSendFriendSettings"/> + <actionGroup ref="AssertAdminEmailToFriendOptionsAvailableActionGroup" stepKey="assertOptions"/> + </test> +</tests> diff --git a/app/code/Magento/Bundle/Model/Product/BundleOptionDataProvider.php b/app/code/Magento/Bundle/Model/Product/BundleOptionDataProvider.php new file mode 100644 index 0000000000000..f56c4228e49e5 --- /dev/null +++ b/app/code/Magento/Bundle/Model/Product/BundleOptionDataProvider.php @@ -0,0 +1,144 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Bundle\Model\Product; + +use Magento\Bundle\Helper\Catalog\Product\Configuration; +use Magento\Bundle\Model\Option; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; +use Magento\Framework\Pricing\Helper\Data; +use Magento\Framework\Serialize\SerializerInterface; + +/** + * Data provider for bundled product options + */ +class BundleOptionDataProvider +{ + /** + * @var Data + */ + private $pricingHelper; + + /** + * @var SerializerInterface + */ + private $serializer; + + /** + * @var Configuration + */ + private $configuration; + + /** + * @param Data $pricingHelper + * @param SerializerInterface $serializer + * @param Configuration $configuration + */ + public function __construct( + Data $pricingHelper, + SerializerInterface $serializer, + Configuration $configuration + ) { + $this->pricingHelper = $pricingHelper; + $this->serializer = $serializer; + $this->configuration = $configuration; + } + + /** + * Extract data for a bundled item + * + * @param ItemInterface $item + * + * @return array + */ + public function getData(ItemInterface $item): array + { + $options = []; + $product = $item->getProduct(); + $optionsQuoteItemOption = $item->getOptionByCode('bundle_option_ids'); + $bundleOptionsIds = $optionsQuoteItemOption + ? $this->serializer->unserialize($optionsQuoteItemOption->getValue()) + : []; + + /** @var Type $typeInstance */ + $typeInstance = $product->getTypeInstance(); + + if ($bundleOptionsIds) { + $selectionsQuoteItemOption = $item->getOptionByCode('bundle_selection_ids'); + $optionsCollection = $typeInstance->getOptionsByIds($bundleOptionsIds, $product); + $bundleSelectionIds = $this->serializer->unserialize($selectionsQuoteItemOption->getValue()); + + if (!empty($bundleSelectionIds)) { + $selectionsCollection = $typeInstance->getSelectionsByIds($bundleSelectionIds, $product); + $bundleOptions = $optionsCollection->appendSelections($selectionsCollection, true); + + $options = $this->buildBundleOptions($bundleOptions, $item); + } + } + + return $options; + } + + /** + * Build bundle product options based on current selection + * + * @param Option[] $bundleOptions + * @param ItemInterface $item + * + * @return array + */ + private function buildBundleOptions(array $bundleOptions, ItemInterface $item): array + { + $options = []; + foreach ($bundleOptions as $bundleOption) { + if (!$bundleOption->getSelections()) { + continue; + } + + $options[] = [ + 'id' => $bundleOption->getId(), + 'label' => $bundleOption->getTitle(), + 'type' => $bundleOption->getType(), + 'values' => $this->buildBundleOptionValues($bundleOption->getSelections(), $item), + ]; + } + + return $options; + } + + /** + * Build bundle product option values based on current selection + * + * @param Product[] $selections + * @param ItemInterface $item + * + * @return array + */ + private function buildBundleOptionValues(array $selections, ItemInterface $item): array + { + $product = $item->getProduct(); + $values = []; + + foreach ($selections as $selection) { + $qty = (float) $this->configuration->getSelectionQty($product, $selection->getSelectionId()); + if (!$qty) { + continue; + } + + $selectionPrice = $this->configuration->getSelectionFinalPrice($item, $selection); + $values[] = [ + 'label' => $selection->getName(), + 'id' => $selection->getSelectionId(), + 'quantity' => $qty, + 'price' => $this->pricingHelper->currency($selectionPrice, false, false), + ]; + } + + return $values; + } +} diff --git a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php index 7b3f6dd8bbefa..303c33b571d35 100644 --- a/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php +++ b/app/code/Magento/Bundle/Model/ResourceModel/Selection/Collection.php @@ -215,7 +215,7 @@ public function joinPrices($websiteId) public function setOptionIdsFilter($optionIds) { if (!empty($optionIds)) { - $this->getSelect()->where('selection.option_id IN (?)', $optionIds); + $this->getSelect()->where('selection.option_id IN (?)', $optionIds, \Zend_Db::INT_TYPE); } return $this; } @@ -229,7 +229,7 @@ public function setOptionIdsFilter($optionIds) public function setSelectionIdsFilter($selectionIds) { if (!empty($selectionIds)) { - $this->getSelect()->where('selection.selection_id IN (?)', $selectionIds); + $this->getSelect()->where('selection.selection_id IN (?)', $selectionIds, \Zend_Db::INT_TYPE); } return $this; } diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml index f6866f813f258..88fc5b7171592 100644 --- a/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml +++ b/app/code/Magento/Bundle/Test/Mftf/Test/StorefrontGoToDetailsPageWhenAddingToCartTest.xml @@ -71,8 +71,7 @@ <!--Click add to cart--> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addProductToCart"/> - <waitForPageLoad stepKey="waitForProductPage"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addProductToCart"/> <!--Check for details page--> <seeInCurrentUrl url="{{BundleProduct.sku}}" stepKey="seeBundleProductDetailsPage"/> diff --git a/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php b/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php index 13bf10bc6aca7..8025cf91d28c9 100644 --- a/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php +++ b/app/code/Magento/BundleGraphQl/Model/Resolver/Links/Collection.php @@ -108,7 +108,7 @@ private function fetch() : array } $linkCollection->getSelect() - ->where($field . ' IN (?)', $this->parentIds); + ->where($field . ' IN (?)', $this->parentIds, \Zend_Db::INT_TYPE); /** @var Selection $link */ foreach ($linkCollection as $link) { diff --git a/app/code/Magento/BundleGraphQl/Model/Wishlist/BundleOptions.php b/app/code/Magento/BundleGraphQl/Model/Wishlist/BundleOptions.php new file mode 100644 index 0000000000000..217f822e771da --- /dev/null +++ b/app/code/Magento/BundleGraphQl/Model/Wishlist/BundleOptions.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\BundleGraphQl\Model\Wishlist; + +use Magento\Bundle\Model\Product\BundleOptionDataProvider; +use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Fetches the selected bundle options + */ +class BundleOptions implements ResolverInterface +{ + /** + * @var BundleOptionDataProvider + */ + private $bundleOptionDataProvider; + + /** + * @param BundleOptionDataProvider $bundleOptionDataProvider + */ + public function __construct( + BundleOptionDataProvider $bundleOptionDataProvider + ) { + $this->bundleOptionDataProvider = $bundleOptionDataProvider; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$value['itemModel'] instanceof ItemInterface) { + throw new LocalizedException(__('"itemModel" should be a "%instance" instance', [ + 'instance' => ItemInterface::class + ])); + } + + return $this->bundleOptionDataProvider->getData($value['itemModel']); + } +} diff --git a/app/code/Magento/BundleGraphQl/etc/graphql/di.xml b/app/code/Magento/BundleGraphQl/etc/graphql/di.xml index 863e152fbe177..7fe0b2a53677c 100644 --- a/app/code/Magento/BundleGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/BundleGraphQl/etc/graphql/di.xml @@ -100,4 +100,11 @@ </argument> </arguments> </type> + <type name="Magento\WishlistGraphQl\Model\Resolver\Type\WishlistItemType"> + <arguments> + <argument name="supportedTypes" xsi:type="array"> + <item name="bundle" xsi:type="string">BundleWishlistItem</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/BundleGraphQl/etc/schema.graphqls b/app/code/Magento/BundleGraphQl/etc/schema.graphqls index a66fa397020a7..a2cba24c7c4d4 100644 --- a/app/code/Magento/BundleGraphQl/etc/schema.graphqls +++ b/app/code/Magento/BundleGraphQl/etc/schema.graphqls @@ -117,3 +117,7 @@ type ItemSelectedBundleOptionValue @doc(description: "A list of values for the s quantity: Float! @doc(description: "Indicates how many of this bundle product were ordered") price: Money! @doc(description: "The price of the child bundle product") } + +type BundleWishlistItem implements WishlistItemInterface { + bundle_options: [SelectedBundleOption!] @doc(description: "An array containing information about the selected bundle items") @resolver(class: "\\Magento\\BundleGraphQl\\Model\\Wishlist\\BundleOptions") +} diff --git a/app/code/Magento/Captcha/CustomerData/Captcha.php b/app/code/Magento/Captcha/CustomerData/Captcha.php index e07bf953abaa3..901477c75610b 100644 --- a/app/code/Magento/Captcha/CustomerData/Captcha.php +++ b/app/code/Magento/Captcha/CustomerData/Captcha.php @@ -58,7 +58,7 @@ public function __construct( /** * @inheritdoc */ - public function getSectionData() :array + public function getSectionData(): array { $data = []; diff --git a/app/code/Magento/CardinalCommerce/Test/Mftf/ActionGroup/AdminOpenAdminThreeDSecurePageActionGroup.xml b/app/code/Magento/CardinalCommerce/Test/Mftf/ActionGroup/AdminOpenAdminThreeDSecurePageActionGroup.xml new file mode 100644 index 0000000000000..baea26921a625 --- /dev/null +++ b/app/code/Magento/CardinalCommerce/Test/Mftf/ActionGroup/AdminOpenAdminThreeDSecurePageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenAdminThreeDSecurePageActionGroup"> + <annotations> + <description>Open ThreeDSecure page.</description> + </annotations> + + <amOnPage url="{{AdminThreeDSecurePage.url}}" stepKey="openAdminThreeDSecurePage"/> + <waitForPageLoad stepKey="waitThreeDSecurePageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Model/CategoryLink.php b/app/code/Magento/Catalog/Model/CategoryLink.php index fe640a72d0b6d..38c6f77483de2 100644 --- a/app/code/Magento/Catalog/Model/CategoryLink.php +++ b/app/code/Magento/Catalog/Model/CategoryLink.php @@ -3,39 +3,41 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Catalog\Model; +use Magento\Catalog\Api\Data\CategoryLinkExtensionInterface; +use Magento\Catalog\Api\Data\CategoryLinkInterface; +use Magento\Framework\Model\AbstractExtensibleModel; + /** * @codeCoverageIgnore */ -class CategoryLink extends \Magento\Framework\Api\AbstractExtensibleObject implements - \Magento\Catalog\Api\Data\CategoryLinkInterface +class CategoryLink extends AbstractExtensibleModel implements CategoryLinkInterface { - /**#@+ - * Constants - */ - const KEY_POSITION = 'position'; - const KEY_CATEGORY_ID = 'category_id'; - /**#@-*/ + public const KEY_POSITION = 'position'; + public const KEY_CATEGORY_ID = 'category_id'; /** - * {@inheritdoc} + * @inheritdoc */ public function getPosition() { - return $this->_get(self::KEY_POSITION); + return $this->getData(self::KEY_POSITION); } /** - * {@inheritdoc} + * @inheritdoc */ public function getCategoryId() { - return $this->_get(self::KEY_CATEGORY_ID); + return $this->getData(self::KEY_CATEGORY_ID); } /** + * @inheritDoc + * * @param int $position * @return $this */ @@ -56,7 +58,7 @@ public function setCategoryId($categoryId) } /** - * {@inheritdoc} + * @inheritdoc * * @return \Magento\Catalog\Api\Data\CategoryLinkExtensionInterface|null */ @@ -66,14 +68,13 @@ public function getExtensionAttributes() } /** - * {@inheritdoc} + * @inheritdoc * * @param \Magento\Catalog\Api\Data\CategoryLinkExtensionInterface $extensionAttributes * @return $this */ - public function setExtensionAttributes( - \Magento\Catalog\Api\Data\CategoryLinkExtensionInterface $extensionAttributes - ) { + public function setExtensionAttributes(CategoryLinkExtensionInterface $extensionAttributes) + { return $this->_setExtensionAttributes($extensionAttributes); } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php index 1506ccf6963bf..ae24b60719ca7 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php @@ -6,10 +6,21 @@ namespace Magento\Catalog\Model\Indexer\Category\Flat; +use Magento\Catalog\Api\Data\CategoryInterface; +use Magento\Catalog\Model\Category; +use Magento\Catalog\Model\ResourceModel\Helper; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Ddl\Table; +use Magento\Framework\EntityManager\EntityMetadata; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; /** * Abstract action class for category flat indexers. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AbstractAction { @@ -31,14 +42,14 @@ class AbstractAction protected $resource; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $storeManager; /** * Catalog resource helper * - * @var \Magento\Catalog\Model\ResourceModel\Helper + * @var Helper */ protected $resourceHelper; @@ -50,12 +61,12 @@ class AbstractAction protected $columns = []; /** - * @var \Magento\Framework\DB\Adapter\AdapterInterface + * @var AdapterInterface */ protected $connection; /** - * @var \Magento\Framework\EntityManager\EntityMetadata + * @var EntityMetadata */ protected $categoryMetadata; @@ -68,13 +79,13 @@ class AbstractAction /** * @param ResourceConnection $resource - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper + * @param StoreManagerInterface $storeManager + * @param Helper $resourceHelper */ public function __construct( ResourceConnection $resource, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper + StoreManagerInterface $storeManager, + Helper $resourceHelper ) { $this->resource = $resource; $this->connection = $resource->getConnection(); @@ -110,23 +121,22 @@ public function getColumns() * @param integer $storeId * @return string */ - public function getMainStoreTable($storeId = \Magento\Store\Model\Store::DEFAULT_STORE_ID) + public function getMainStoreTable($storeId = Store::DEFAULT_STORE_ID) { if (is_string($storeId)) { $storeId = (int) $storeId; } $suffix = sprintf('store_%d', $storeId); - $table = $this->connection->getTableName($this->getTableName('catalog_category_flat_' . $suffix)); - - return $table; + return $this->connection->getTableName($this->getTableName('catalog_category_flat_' . $suffix)); } /** * Return structure for flat catalog table * * @param string $tableName - * @return \Magento\Framework\DB\Ddl\Table + * @return Table + * @throws \Zend_Db_Exception */ protected function getFlatTableStructure($tableName) { @@ -139,10 +149,10 @@ protected function getFlatTableStructure($tableName) //Adding columns foreach ($this->getColumns() as $fieldName => $fieldProp) { $default = $fieldProp['default']; - if ($fieldProp['type'][0] == \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP + if ($fieldProp['type'][0] == Table::TYPE_TIMESTAMP && $default == 'CURRENT_TIMESTAMP' ) { - $default = \Magento\Framework\DB\Ddl\Table::TIMESTAMP_INIT; + $default = Table::TIMESTAMP_INIT; } $table->addColumn( $fieldName, @@ -205,9 +215,9 @@ protected function getStaticColumns() $ddlType = $this->resourceHelper->getDdlTypeByColumnType($column['DATA_TYPE']); $column['DEFAULT'] = trim($column['DEFAULT'], "' "); switch ($ddlType) { - case \Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT: - case \Magento\Framework\DB\Ddl\Table::TYPE_INTEGER: - case \Magento\Framework\DB\Ddl\Table::TYPE_BIGINT: + case Table::TYPE_SMALLINT: + case Table::TYPE_INTEGER: + case Table::TYPE_BIGINT: $isUnsigned = (bool)$column['UNSIGNED']; if ($column['DEFAULT'] === '') { $column['DEFAULT'] = null; @@ -215,27 +225,27 @@ protected function getStaticColumns() $options = null; if ($column['SCALE'] > 0) { - $ddlType = \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL; + $ddlType = Table::TYPE_DECIMAL; } else { break; } // fall-through intentional - case \Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL: + case Table::TYPE_DECIMAL: $options = $column['PRECISION'] . ',' . $column['SCALE']; $isUnsigned = null; if ($column['DEFAULT'] === '') { $column['DEFAULT'] = null; } break; - case \Magento\Framework\DB\Ddl\Table::TYPE_TEXT: + case Table::TYPE_TEXT: $options = $column['LENGTH']; $isUnsigned = null; break; - case \Magento\Framework\DB\Ddl\Table::TYPE_TIMESTAMP: + case Table::TYPE_TIMESTAMP: $options = null; $isUnsigned = null; break; - case \Magento\Framework\DB\Ddl\Table::TYPE_DATETIME: + case Table::TYPE_DATETIME: $isUnsigned = null; break; } @@ -248,7 +258,7 @@ protected function getStaticColumns() ]; } $columns['store_id'] = [ - 'type' => [\Magento\Framework\DB\Ddl\Table::TYPE_SMALLINT, 5], + 'type' => [Table::TYPE_SMALLINT, 5], 'unsigned' => true, 'nullable' => false, 'default' => '0', @@ -274,7 +284,7 @@ protected function getEavColumns() switch ($attribute['backend_type']) { case 'varchar': $columns[$attribute['attribute_code']] = [ - 'type' => [\Magento\Framework\DB\Ddl\Table::TYPE_TEXT, 255], + 'type' => [Table::TYPE_TEXT, 255], 'unsigned' => null, 'nullable' => true, 'default' => null, @@ -283,7 +293,7 @@ protected function getEavColumns() break; case 'int': $columns[$attribute['attribute_code']] = [ - 'type' => [\Magento\Framework\DB\Ddl\Table::TYPE_INTEGER, null], + 'type' => [Table::TYPE_INTEGER, null], 'unsigned' => null, 'nullable' => true, 'default' => null, @@ -292,7 +302,7 @@ protected function getEavColumns() break; case 'text': $columns[$attribute['attribute_code']] = [ - 'type' => [\Magento\Framework\DB\Ddl\Table::TYPE_TEXT, '64k'], + 'type' => [Table::TYPE_TEXT, '64k'], 'unsigned' => null, 'nullable' => true, 'default' => null, @@ -301,7 +311,7 @@ protected function getEavColumns() break; case 'datetime': $columns[$attribute['attribute_code']] = [ - 'type' => [\Magento\Framework\DB\Ddl\Table::TYPE_DATETIME, null], + 'type' => [Table::TYPE_DATETIME, null], 'unsigned' => null, 'nullable' => true, 'default' => null, @@ -310,7 +320,7 @@ protected function getEavColumns() break; case 'decimal': $columns[$attribute['attribute_code']] = [ - 'type' => [\Magento\Framework\DB\Ddl\Table::TYPE_DECIMAL, '12,4'], + 'type' => [Table::TYPE_DECIMAL, '12,4'], 'unsigned' => null, 'nullable' => true, 'default' => null, @@ -346,7 +356,7 @@ protected function getAttributes() $this->connection->getTableName( $this->getTableName('eav_entity_type') ) . '.entity_type_code = ?', - \Magento\Catalog\Model\Category::ENTITY + Category::ENTITY ); $this->attributeCodes = []; foreach ($this->connection->fetchAll($select) as $attribute) { @@ -414,7 +424,8 @@ private function getLinkIds(array $entityIds) [$linkField] )->where( 'e.entity_id IN (?)', - $entityIds + $entityIds, + \Zend_Db::INT_TYPE ); return $this->connection->fetchCol($select); @@ -459,10 +470,12 @@ protected function getAttributeTypeValues($type, $entityIds, $storeId) ] )->where( "e.entity_id IN (?)", - $entityIds + $entityIds, + \Zend_Db::INT_TYPE )->where( 'def.store_id IN (?)', - [\Magento\Store\Model\Store::DEFAULT_STORE_ID, $storeId] + [Store::DEFAULT_STORE_ID, $storeId], + \Zend_Db::INT_TYPE ); return $this->connection->fetchAll($select); @@ -501,14 +514,14 @@ protected function getTableName($name) /** * Get category metadata instance. * - * @return \Magento\Framework\EntityManager\EntityMetadata + * @return EntityMetadata */ private function getCategoryMetadata() { if (null === $this->categoryMetadata) { - $metadataPool = \Magento\Framework\App\ObjectManager::getInstance() + $metadataPool = ObjectManager::getInstance() ->get(\Magento\Framework\EntityManager\MetadataPool::class); - $this->categoryMetadata = $metadataPool->getMetadata(\Magento\Catalog\Api\Data\CategoryInterface::class); + $this->categoryMetadata = $metadataPool->getMetadata(CategoryInterface::class); } return $this->categoryMetadata; } @@ -521,8 +534,8 @@ private function getCategoryMetadata() private function getSkipStaticColumns() { if (null === $this->skipStaticColumns) { - $provider = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Catalog\Model\Indexer\Category\Flat\SkipStaticColumnsProvider::class); + $provider = ObjectManager::getInstance() + ->get(SkipStaticColumnsProvider::class); $this->skipStaticColumns = $provider->get(); } return $this->skipStaticColumns; diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Rows.php index c722206193eb3..20f01e4b0a0ab 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Action/Rows.php @@ -119,7 +119,8 @@ protected function filterIdsByStore(array $ids, $store) "path = {$rootIdExpr} OR path = {$rootCatIdExpr} OR path like {$catIdExpr}" )->where( "entity_id IN (?)", - $ids + $ids, + \Zend_Db::INT_TYPE ); $resultIds = []; @@ -170,27 +171,30 @@ private function buildIndexData(Store $store, $categoriesIdsChunk, $attributesDa foreach ($categoriesIdsChunk as $categoryId) { try { $category = $this->categoryRepository->get($categoryId); - $categoryData = $category->getData(); - $linkId = $categoryData[$linkField]; - - $categoryAttributesData = []; - if (isset($attributesData[$linkId]) && is_array($attributesData[$linkId])) { - $categoryAttributesData = $attributesData[$linkId]; - } - $categoryIndexData = $this->buildCategoryIndexData( - $store, - $categoryData, - $categoryAttributesData - ); - $data[] = $categoryIndexData; } catch (NoSuchEntityException $e) { - // ignore + continue; } + + $categoryData = $category->getData(); + $linkId = $categoryData[$linkField]; + + $categoryAttributesData = []; + if (isset($attributesData[$linkId]) && is_array($attributesData[$linkId])) { + $categoryAttributesData = $attributesData[$linkId]; + } + $categoryIndexData = $this->buildCategoryIndexData( + $store, + $categoryData, + $categoryAttributesData + ); + $data[] = $categoryIndexData; } return $data; } /** + * Prepare Category data + * * @param Store $store * @param array $categoryData * @param array $categoryAttributesData @@ -213,7 +217,8 @@ private function buildCategoryIndexData(Store $store, array $categoryData, array * Insert or update index data * * @param string $tableName - * @param $data + * @param array $data + * @return void */ private function updateIndexData($tableName, $data) { diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Plugin/StoreGroup.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Plugin/StoreGroup.php index 670f19db725f7..16f96d794a97b 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Plugin/StoreGroup.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/Plugin/StoreGroup.php @@ -5,18 +5,13 @@ */ namespace Magento\Catalog\Model\Indexer\Category\Flat\Plugin; -use Magento\Framework\Model\ResourceModel\Db\AbstractDb; -use Magento\Framework\Model\AbstractModel; -use Magento\Framework\Indexer\IndexerRegistry; use Magento\Catalog\Model\Indexer\Category\Flat\State; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; class StoreGroup { - /** - * @var bool - */ - private $needInvalidating; - /** * @var IndexerRegistry */ @@ -48,35 +43,21 @@ protected function validate(AbstractModel $group) return $group->dataHasChangedFor('root_category_id') && !$group->isObjectNew(); } - /** - * Check if need invalidate flat category indexer - * - * @param AbstractDb $subject - * @param AbstractModel $group - * - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeSave(AbstractDb $subject, AbstractModel $group) - { - $this->needInvalidating = $this->validate($group); - } - /** * Invalidate flat category indexer if root category changed for store group * * @param AbstractDb $subject - * @param AbstractDb $objectResource - * + * @param AbstractDb $result + * @param AbstractModel $group * @return AbstractDb * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterSave(AbstractDb $subject, AbstractDb $objectResource) + public function afterSave(AbstractDb $subject, AbstractDb $result, AbstractModel $group) { - if ($this->needInvalidating && $this->state->isFlatEnabled()) { + if ($this->validate($group) && $this->state->isFlatEnabled()) { $this->indexerRegistry->get(State::INDEXER_ID)->invalidate(); } - return $objectResource; + return $result; } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php index 6b6ad2bfc726a..38f606b8abefe 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/AbstractAction.php @@ -15,6 +15,7 @@ use Magento\Framework\EntityManager\MetadataPool; use Magento\Store\Model\Store; +// phpcs:disable Magento2.Classes.AbstractApi /** * Class AbstractAction * @@ -42,7 +43,7 @@ abstract class AbstractAction /** * Suffix for table to show it is temporary - * @deprecated + * @deprecated see getIndexTable */ const TEMPORARY_TABLE_SUFFIX = '_tmp'; @@ -504,10 +505,11 @@ protected function createAnchorSelect(Store $store) [] )->joinInner( ['cc2' => $temporaryTreeTable], - 'cc2.parent_id = cc.entity_id AND cc.entity_id NOT IN (' . implode( - ',', - $rootCatIds - ) . ')', + $this->connection->quoteInto( + 'cc2.parent_id = cc.entity_id AND cc.entity_id NOT IN (?)', + $rootCatIds, + \Zend_Db::INT_TYPE + ), [] )->joinInner( ['ccp' => $this->getTable('catalog_category_product')], diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php index eee347c36910d..a7c5cdf412e6e 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php @@ -174,7 +174,7 @@ protected function reindex(): void foreach ($this->storeManager->getStores() as $store) { if ($this->getPathFromCategoryId($store->getRootCategoryId())) { $userFunctions[$store->getId()] = function () use ($store) { - return $this->reindexStore($store); + $this->reindexStore($store); }; } } @@ -282,7 +282,7 @@ private function reindexCategoriesBySelect(Select $basicSelect, $whereCondition, $this->connection->delete($this->tableMaintainer->getMainTmpTable((int)$store->getId())); $entityIds = $this->connection->fetchCol($query); $resultSelect = clone $basicSelect; - $resultSelect->where($whereCondition, $entityIds); + $resultSelect->where($whereCondition, $entityIds, \Zend_Db::INT_TYPE); $this->connection->query( $this->connection->insertFromSelect( $resultSelect, diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php index 005936a75e6d6..12a9d85dc416b 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php @@ -5,19 +5,14 @@ */ namespace Magento\Catalog\Model\Indexer\Category\Product\Plugin; -use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Framework\Model\ResourceModel\Db\AbstractDb; -use Magento\Framework\Model\AbstractModel; use Magento\Catalog\Model\Indexer\Category\Product; use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; class StoreGroup { - /** - * @var bool - */ - private $needInvalidating; - /** * @var IndexerRegistry */ @@ -40,36 +35,23 @@ public function __construct( $this->tableMaintainer = $tableMaintainer; } - /** - * Check if need invalidate flat category indexer - * - * @param AbstractDb $subject - * @param AbstractModel $group - * - * @return void - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeSave(AbstractDb $subject, AbstractModel $group) - { - $this->needInvalidating = $this->validate($group); - } - /** * Invalidate flat product * * @param AbstractDb $subject - * @param AbstractDb $objectResource + * @param AbstractDb $result + * @param AbstractModel $group * * @return AbstractDb * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterSave(AbstractDb $subject, AbstractDb $objectResource) + public function afterSave(AbstractDb $subject, AbstractDb $result, AbstractModel $group) { - if ($this->needInvalidating) { + if ($this->validate($group)) { $this->indexerRegistry->get(Product::INDEXER_ID)->invalidate(); } - return $objectResource; + return $result; } /** diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php index b6f9e6adf4a1c..78353f56a0e13 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php +++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php @@ -5,18 +5,18 @@ */ namespace Magento\Catalog\Model\Indexer\Category\Product\Plugin; -use Magento\Framework\Model\ResourceModel\Db\AbstractDb; use Magento\Framework\Model\AbstractModel; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; class StoreView extends StoreGroup { /** * Validate changes for invalidating indexer * - * @param \Magento\Framework\Model\AbstractModel $store + * @param AbstractModel $store * @return bool */ - protected function validate(\Magento\Framework\Model\AbstractModel $store) + protected function validate(AbstractModel $store) { return $store->isObjectNew() || $store->dataHasChangedFor('group_id'); } @@ -36,7 +36,7 @@ public function afterSave(AbstractDb $subject, AbstractDb $objectResource, Abstr $this->tableMaintainer->createTablesForStore($store->getId()); } - return parent::afterSave($subject, $objectResource); + return parent::afterSave($subject, $objectResource, $store); } /** diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php index ec3d0d57330ec..edd68422ec4ac 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php @@ -151,7 +151,7 @@ private function getProductIdsWithParents(array $childProductIds): array ->select() ->from(['relation' => $this->getTable('catalog_product_relation')], []) ->distinct(true) - ->where('child_id IN (?)', $childProductIds) + ->where('child_id IN (?)', $childProductIds, \Zend_Db::INT_TYPE) ->join( ['cpe' => $this->getTable('catalog_product_entity')], 'relation.parent_id = cpe.' . $fieldForParent, @@ -215,7 +215,7 @@ protected function removeEntries() protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $store) { $select = parent::getNonAnchorCategoriesSelect($store); - return $select->where('ccp.product_id IN (?)', $this->limitationByProducts); + return $select->where('ccp.product_id IN (?)', $this->limitationByProducts, \Zend_Db::INT_TYPE); } /** @@ -227,7 +227,7 @@ protected function getNonAnchorCategoriesSelect(\Magento\Store\Model\Store $stor protected function getAnchorCategoriesSelect(\Magento\Store\Model\Store $store) { $select = parent::getAnchorCategoriesSelect($store); - return $select->where('ccp.product_id IN (?)', $this->limitationByProducts); + return $select->where('ccp.product_id IN (?)', $this->limitationByProducts, \Zend_Db::INT_TYPE); } /** @@ -239,7 +239,7 @@ protected function getAnchorCategoriesSelect(\Magento\Store\Model\Store $store) protected function getAllProducts(\Magento\Store\Model\Store $store) { $select = parent::getAllProducts($store); - return $select->where('cp.entity_id IN (?)', $this->limitationByProducts); + return $select->where('cp.entity_id IN (?)', $this->limitationByProducts, \Zend_Db::INT_TYPE); } /** @@ -265,7 +265,7 @@ private function getCategoryIdsFromIndex(array $productIds): array $storeCategories = $this->connection->fetchCol( $this->connection->select() ->from($this->getIndexTable($store->getId()), ['category_id']) - ->where('product_id IN (?)', $productIds) + ->where('product_id IN (?)', $productIds, \Zend_Db::INT_TYPE) ->distinct() ); $categoryIds[] = $storeCategories; diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Plugin/StoreView.php b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Plugin/StoreView.php index 95e525b2601f4..4503ca5ea23c7 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Plugin/StoreView.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Plugin/StoreView.php @@ -3,21 +3,26 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Catalog\Model\Indexer\Product\Eav\Plugin; +use Magento\Catalog\Model\Indexer\Product\Eav\Processor; +use Magento\Framework\Model\AbstractModel; +use Magento\Store\Model\ResourceModel\Store; + class StoreView { /** * Product attribute indexer processor * - * @var \Magento\Catalog\Model\Indexer\Product\Eav\Processor + * @var Processor */ protected $_indexerEavProcessor; /** - * @param \Magento\Catalog\Model\Indexer\Product\Eav\Processor $indexerEavProcessor + * @param Processor $indexerEavProcessor */ - public function __construct(\Magento\Catalog\Model\Indexer\Product\Eav\Processor $indexerEavProcessor) + public function __construct(Processor $indexerEavProcessor) { $this->_indexerEavProcessor = $indexerEavProcessor; } @@ -25,18 +30,19 @@ public function __construct(\Magento\Catalog\Model\Indexer\Product\Eav\Processor /** * Before save handler * - * @param \Magento\Store\Model\ResourceModel\Store $subject - * @param \Magento\Framework\Model\AbstractModel $object + * @param Store $subject + * @param Store $result + * @param AbstractModel $object * - * @return void + * @return Store * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeSave( - \Magento\Store\Model\ResourceModel\Store $subject, - \Magento\Framework\Model\AbstractModel $object - ) { - if ((!$object->getId() || $object->dataHasChangedFor('group_id')) && $object->getIsActive()) { + public function afterSave(Store $subject, Store $result, AbstractModel $object) + { + if (($object->isObjectNew() || $object->dataHasChangedFor('group_id')) && $object->getIsActive()) { $this->_indexerEavProcessor->markIndexerAsInvalid(); } + + return $result; } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php index 2252b3e3d5506..99d75186eca8c 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/FlatTableBuilder.php @@ -6,8 +6,19 @@ namespace Magento\Catalog\Model\Indexer\Product\Flat; use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Helper\Product\Flat\Indexer; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ResourceConnection; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Ddl\Table; use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Framework\App\ObjectManager; +use Magento\Eav\Model\Entity\Attribute; +use Magento\Framework\DB\Select; /** * Class for building flat index @@ -27,22 +38,22 @@ class FlatTableBuilder const XML_NODE_MAX_INDEX_COUNT = 'catalog/product/flat/max_index_count'; /** - * @var \Magento\Catalog\Helper\Product\Flat\Indexer + * @var Indexer */ protected $_productIndexerHelper; /** - * @var \Magento\Framework\DB\Adapter\AdapterInterface + * @var AdapterInterface */ protected $_connection; /** - * @var \Magento\Framework\App\Config\ScopeConfigInterface $config + * @var ScopeConfigInterface $config */ protected $_config; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $_storeManager; @@ -52,23 +63,23 @@ class FlatTableBuilder protected $_tableData; /** - * @var \Magento\Framework\App\ResourceConnection + * @var ResourceConnection */ protected $resource; /** - * @param \Magento\Catalog\Helper\Product\Flat\Indexer $productIndexerHelper + * @param Indexer $productIndexerHelper * @param ResourceConnection $resource - * @param \Magento\Framework\App\Config\ScopeConfigInterface $config - * @param \Magento\Store\Model\StoreManagerInterface $storeManager + * @param ScopeConfigInterface $config + * @param StoreManagerInterface $storeManager * @param TableDataInterface $tableData */ public function __construct( - \Magento\Catalog\Helper\Product\Flat\Indexer $productIndexerHelper, - \Magento\Framework\App\ResourceConnection $resource, - \Magento\Framework\App\Config\ScopeConfigInterface $config, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Catalog\Model\Indexer\Product\Flat\TableDataInterface $tableData + Indexer $productIndexerHelper, + ResourceConnection $resource, + ScopeConfigInterface $config, + StoreManagerInterface $storeManager, + TableDataInterface $tableData ) { $this->_productIndexerHelper = $productIndexerHelper; $this->resource = $resource; @@ -114,7 +125,7 @@ public function build($storeId, $changedIds, $valueFieldSuffix, $tableDropSuffix * * @param int|string $storeId * @return void - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) */ @@ -128,7 +139,7 @@ protected function _createTemporaryFlatTable($storeId) self::XML_NODE_MAX_INDEX_COUNT ); if ($maxIndex && count($indexesNeed) > $maxIndex) { - throw new \Magento\Framework\Exception\LocalizedException( + throw new LocalizedException( __( 'The Flat Catalog module has a limit of %2$d filterable and/or sortable attributes.' . 'Currently there are %1$d of them.' @@ -141,7 +152,7 @@ protected function _createTemporaryFlatTable($storeId) $indexKeys = []; $indexProps = array_values($indexesNeed); - $upperPrimaryKey = strtoupper(\Magento\Framework\DB\Adapter\AdapterInterface::INDEX_TYPE_PRIMARY); + $upperPrimaryKey = strtoupper(AdapterInterface::INDEX_TYPE_PRIMARY); foreach ($indexProps as $i => $indexProp) { $indexName = $this->_connection->getIndexName( $this->_getTemporaryTableName($this->_productIndexerHelper->getFlatTableName($storeId)), @@ -164,7 +175,7 @@ protected function _createTemporaryFlatTable($storeId) } $indexesNeed = array_combine($indexKeys, $indexProps); - /** @var $table \Magento\Framework\DB\Ddl\Table */ + /** @var $table Table */ $table = $this->_connection->newTable( $this->_getTemporaryTableName($this->_productIndexerHelper->getFlatTableName($storeId)) ); @@ -211,6 +222,8 @@ protected function _createTemporaryFlatTable($storeId) * @param int|string $storeId * @param string $valueFieldSuffix * @return void + * @throws LocalizedException + * @throws NoSuchEntityException */ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldSuffix) { @@ -226,14 +239,14 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS $websiteId = (int)$this->_storeManager->getStore($storeId)->getWebsiteId(); unset($tables[$entityTableName]); - - $allColumns = array_values( + $allColumns = []; + $allColumns[] = array_values( array_unique( array_merge(['entity_id', $linkField, 'type_id', 'attribute_set_id'], $columnsList) ) ); - /* @var $status \Magento\Eav\Model\Entity\Attribute */ + /* @var $status Attribute */ $status = $this->_productIndexerHelper->getAttribute('status'); $statusTable = $this->_getTemporaryTableName($status->getBackendTable()); $statusConditions = [ @@ -248,7 +261,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS $select->from( ['et' => $entityTemporaryTableName], - $allColumns + array_merge(...$allColumns) )->joinInner( ['e' => $this->resource->getTableName('catalog_product_entity')], 'e.entity_id = et.entity_id', @@ -262,7 +275,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS implode(' AND ', $statusConditions), [] )->where( - $statusExpression . ' = ' . \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + $statusExpression . ' = ' . Status::STATUS_ENABLED ); foreach ($tables as $tableName => $columns) { @@ -276,7 +289,7 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryTableName), $columnsNames ); - $allColumns = array_merge($allColumns, $columnsNames); + $allColumns[] = $columnsNames; foreach ($columnsNames as $name) { $columnValueName = $name . $valueFieldSuffix; @@ -290,10 +303,10 @@ protected function _fillTemporaryFlatTable(array $tables, $storeId, $valueFieldS sprintf('e.%1$s = %2$s.%1$s', $linkField, $temporaryValueTableName), $columnValueNames ); - $allColumns = array_merge($allColumns, $columnValueNames); + $allColumns[] = $columnValueNames; } } - $sql = $select->insertFromSelect($temporaryFlatTableName, $allColumns, false); + $sql = $select->insertFromSelect($temporaryFlatTableName, array_merge(...$allColumns), false); $this->_connection->query($sql); } @@ -319,7 +332,7 @@ protected function _updateTemporaryTableByStoreValues( $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField(); foreach ($tables as $tableName => $columns) { foreach ($columns as $attribute) { - /* @var $attribute \Magento\Eav\Model\Entity\Attribute */ + /* @var $attribute Attribute */ $attributeCode = $attribute->getAttributeCode(); if ($attribute->getBackend()->getType() != 'static') { $joinCondition = sprintf('t.%s = e.%s', $linkField, $linkField) . @@ -328,7 +341,7 @@ protected function _updateTemporaryTableByStoreValues( ' AND t.store_id = ' . $storeId . ' AND t.value IS NOT NULL'; - /** @var $select \Magento\Framework\DB\Select */ + /** @var $select Select */ $select = $this->_connection->select() ->joinInner( ['e' => $this->resource->getTableName('catalog_product_entity')], @@ -340,7 +353,9 @@ protected function _updateTemporaryTableByStoreValues( [$attributeCode => 't.value'] ); if (!empty($changedIds)) { - $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds)); + $select->where( + $this->_connection->quoteInto('et.entity_id IN (?)', $changedIds, \Zend_Db::INT_TYPE) + ); } $sql = $select->crossUpdateFromSelect(['et' => $temporaryFlatTableName]); $this->_connection->query($sql); @@ -363,7 +378,13 @@ protected function _updateTemporaryTableByStoreValues( [$columnName => $columnValue] )->where($columnValue . ' IS NOT NULL'); if (!empty($changedIds)) { - $select->where($this->_connection->quoteInto('et.entity_id IN (?)', $changedIds)); + $select->where( + $this->_connection->quoteInto( + 'et.entity_id IN (?)', + $changedIds, + \Zend_Db::INT_TYPE + ) + ); } $sql = $select->crossUpdateFromSelect(['et' => $temporaryFlatTableName]); $this->_connection->query($sql); @@ -386,13 +407,13 @@ protected function _getTemporaryTableName($tableName) /** * Get metadata pool * - * @return \Magento\Framework\EntityManager\MetadataPool + * @return MetadataPool */ private function getMetadataPool() { if (null === $this->metadataPool) { - $this->metadataPool = \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\Framework\EntityManager\MetadataPool::class); + $this->metadataPool = ObjectManager::getInstance() + ->get(MetadataPool::class); } return $this->metadataPool; } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/Store.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/Store.php index ef7919193e609..38b9281f42cca 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/Store.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/Store.php @@ -6,19 +6,23 @@ namespace Magento\Catalog\Model\Indexer\Product\Flat\Plugin; +use Magento\Catalog\Model\Indexer\Product\Flat\Processor; +use Magento\Framework\Model\AbstractModel; +use Magento\Store\Model\ResourceModel\Store as StoreResourceModel; + class Store { /** * Product flat indexer processor * - * @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor + * @var Processor */ protected $_productFlatIndexerProcessor; /** - * @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor + * @param Processor $productFlatIndexerProcessor */ - public function __construct(\Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor) + public function __construct(Processor $productFlatIndexerProcessor) { $this->_productFlatIndexerProcessor = $productFlatIndexerProcessor; } @@ -26,18 +30,19 @@ public function __construct(\Magento\Catalog\Model\Indexer\Product\Flat\Processo /** * Before save handler * - * @param \Magento\Store\Model\ResourceModel\Store $subject - * @param \Magento\Framework\Model\AbstractModel $object + * @param StoreResourceModel $subject + * @param StoreResourceModel $result + * @param AbstractModel $object * - * @return void + * @return StoreResourceModel * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeSave( - \Magento\Store\Model\ResourceModel\Store $subject, - \Magento\Framework\Model\AbstractModel $object - ) { - if (!$object->getId() || $object->dataHasChangedFor('group_id')) { + public function afterSave(StoreResourceModel $subject, StoreResourceModel $result, AbstractModel $object) + { + if ($object->isObjectNew() || $object->dataHasChangedFor('group_id')) { $this->_productFlatIndexerProcessor->markIndexerAsInvalid(); } + + return $result; } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/StoreGroup.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/StoreGroup.php index df62fe8d349e4..1276e60ac74b0 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/StoreGroup.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Plugin/StoreGroup.php @@ -6,19 +6,23 @@ namespace Magento\Catalog\Model\Indexer\Product\Flat\Plugin; +use Magento\Catalog\Model\Indexer\Product\Flat\Processor; +use Magento\Framework\Model\AbstractModel; +use Magento\Store\Model\ResourceModel\Group; + class StoreGroup { /** * Product flat indexer processor * - * @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor + * @var Processor */ protected $_productFlatIndexerProcessor; /** - * @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor + * @param Processor $productFlatIndexerProcessor */ - public function __construct(\Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor) + public function __construct(Processor $productFlatIndexerProcessor) { $this->_productFlatIndexerProcessor = $productFlatIndexerProcessor; } @@ -26,18 +30,19 @@ public function __construct(\Magento\Catalog\Model\Indexer\Product\Flat\Processo /** * Before save handler * - * @param \Magento\Store\Model\ResourceModel\Group $subject - * @param \Magento\Framework\Model\AbstractModel $object + * @param Group $subject + * @param Group $result + * @param AbstractModel $object * - * @return void + * @return Group * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeSave( - \Magento\Store\Model\ResourceModel\Group $subject, - \Magento\Framework\Model\AbstractModel $object - ) { - if (!$object->getId() || $object->dataHasChangedFor('root_category_id')) { + public function afterSave(Group $subject, Group $result, AbstractModel $object) + { + if ($object->isObjectNew() || $object->dataHasChangedFor('root_category_id')) { $this->_productFlatIndexerProcessor->markIndexerAsInvalid(); } + + return $result; } } diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php index fb9c8aace8d7d..23eaf7d7b2010 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Table/Builder.php @@ -6,7 +6,7 @@ namespace Magento\Catalog\Model\Indexer\Product\Flat\Table; /** - * Class Builder + * Build table structure based on provided columns */ class Builder implements BuilderInterface { diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php index 0897ae0d74c0b..c14ea4bc363f8 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/TableBuilder.php @@ -9,7 +9,7 @@ use Magento\Store\Model\Store; /** - * Class TableBuilder + * Prepare temporary tables structure for product flat indexer * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ @@ -347,7 +347,9 @@ protected function _fillTemporaryTable( } if (!empty($changedIds)) { - $select->where($this->_connection->quoteInto('e.entity_id IN (?)', $changedIds)); + $select->where( + $this->_connection->quoteInto('e.entity_id IN (?)', $changedIds, \Zend_Db::INT_TYPE) + ); } $sql = $select->insertFromSelect($temporaryTableName, $columns, true); @@ -355,7 +357,9 @@ protected function _fillTemporaryTable( if (count($valueColumns) > 1) { if (!empty($changedIds)) { - $selectValue->where($this->_connection->quoteInto('e.entity_id IN (?)', $changedIds)); + $selectValue->where( + $this->_connection->quoteInto('e.entity_id IN (?)', $changedIds, \Zend_Db::INT_TYPE) + ); } $sql = $selectValue->insertFromSelect($temporaryValueTableName, $valueColumns, true); $this->_connection->query($sql); diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php index 41e72ecf880a5..f3a4b322e29df 100644 --- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php +++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php @@ -3,13 +3,31 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\Catalog\Model\Indexer\Product\Price; +use Magento\Catalog\Model\Product\Type; use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface; +use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice; use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider; +use Magento\Directory\Model\Currency; +use Magento\Directory\Model\CurrencyFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Indexer\DimensionalIndexerInterface; +use Magento\Framework\Search\Request\Dimension; +use Magento\Framework\Stdlib\DateTime; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\Store\Model\Indexer\WebsiteDimensionProvider; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\Website; /** * Abstract action reindex class @@ -26,48 +44,48 @@ abstract class AbstractAction protected $_defaultIndexerResource; /** - * @var \Magento\Framework\DB\Adapter\AdapterInterface + * @var AdapterInterface */ protected $_connection; /** * Core config model * - * @var \Magento\Framework\App\Config\ScopeConfigInterface + * @var ScopeConfigInterface */ protected $_config; /** - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface */ protected $_storeManager; /** * Currency factory * - * @var \Magento\Directory\Model\CurrencyFactory + * @var CurrencyFactory */ protected $_currencyFactory; /** - * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface + * @var TimezoneInterface */ protected $_localeDate; /** - * @var \Magento\Framework\Stdlib\DateTime + * @var DateTime */ protected $_dateTime; /** - * @var \Magento\Catalog\Model\Product\Type + * @var Type */ protected $_catalogProductType; /** * Indexer price factory * - * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory + * @var Factory */ protected $_indexerPriceFactory; @@ -77,12 +95,12 @@ abstract class AbstractAction protected $_indexers; /** - * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice + * @var TierPrice */ private $tierPriceIndexResource; /** - * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory + * @var DimensionCollectionFactory */ private $dimensionCollectionFactory; @@ -92,15 +110,15 @@ abstract class AbstractAction private $tableMaintainer; /** - * @param \Magento\Framework\App\Config\ScopeConfigInterface $config - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @param \Magento\Framework\Stdlib\DateTime $dateTime - * @param \Magento\Catalog\Model\Product\Type $catalogProductType - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory + * @param ScopeConfigInterface $config + * @param StoreManagerInterface $storeManager + * @param CurrencyFactory $currencyFactory + * @param TimezoneInterface $localeDate + * @param DateTime $dateTime + * @param Type $catalogProductType + * @param Factory $indexerPriceFactory * @param DefaultPrice $defaultIndexerResource - * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice|null $tierPriceIndexResource + * @param TierPrice|null $tierPriceIndexResource * @param DimensionCollectionFactory|null $dimensionCollectionFactory * @param TableMaintainer|null $tableMaintainer * @SuppressWarnings(PHPMD.NPathComplexity) @@ -108,17 +126,17 @@ abstract class AbstractAction * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( - \Magento\Framework\App\Config\ScopeConfigInterface $config, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Directory\Model\CurrencyFactory $currencyFactory, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, - \Magento\Framework\Stdlib\DateTime $dateTime, - \Magento\Catalog\Model\Product\Type $catalogProductType, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory, + ScopeConfigInterface $config, + StoreManagerInterface $storeManager, + CurrencyFactory $currencyFactory, + TimezoneInterface $localeDate, + DateTime $dateTime, + Type $catalogProductType, + Factory $indexerPriceFactory, DefaultPrice $defaultIndexerResource, - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource = null, - \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory = null, - \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $tableMaintainer = null + TierPrice $tierPriceIndexResource = null, + DimensionCollectionFactory $dimensionCollectionFactory = null, + TableMaintainer $tableMaintainer = null ) { $this->_config = $config; $this->_storeManager = $storeManager; @@ -130,13 +148,13 @@ public function __construct( $this->_defaultIndexerResource = $defaultIndexerResource; $this->_connection = $this->_defaultIndexerResource->getConnection(); $this->tierPriceIndexResource = $tierPriceIndexResource ?? ObjectManager::getInstance()->get( - \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice::class + TierPrice::class ); $this->dimensionCollectionFactory = $dimensionCollectionFactory ?? ObjectManager::getInstance()->get( - \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class + DimensionCollectionFactory::class ); $this->tableMaintainer = $tableMaintainer ?? ObjectManager::getInstance()->get( - \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class + TableMaintainer::class ); } @@ -152,7 +170,7 @@ abstract public function execute($ids); * Synchronize data between index storage and original storage * * @param array $processIds - * @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction + * @return AbstractAction * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @deprecated 102.0.6 Used only for backward compatibility for indexer, which not support indexation by dimensions */ @@ -182,14 +200,14 @@ protected function _syncData(array $processIds = []) /** * Prepare website current dates table * - * @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction + * @return AbstractAction * - * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws LocalizedException + * @throws NoSuchEntityException */ protected function _prepareWebsiteDateTable() { - $baseCurrency = $this->_config->getValue(\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE); + $baseCurrency = $this->_config->getValue(Currency::XML_PATH_CURRENCY_BASE); $select = $this->getConnection()->select()->from( ['cw' => $this->_defaultIndexerResource->getTable('store_website')], @@ -204,7 +222,7 @@ protected function _prepareWebsiteDateTable() $data = []; foreach ($this->getConnection()->fetchAll($select) as $item) { - /** @var $website \Magento\Store\Model\Website */ + /** @var $website Website */ $website = $this->_storeManager->getWebsite($item['website_id']); if ($website->getBaseCurrencyCode() != $baseCurrency) { @@ -220,7 +238,7 @@ protected function _prepareWebsiteDateTable() $rate = 1; } - /** @var $store \Magento\Store\Model\Store */ + /** @var $store Store */ $store = $this->_storeManager->getStore($item['store_id']); if ($store) { $timestamp = $this->_localeDate->scopeTimeStamp($store); @@ -248,11 +266,11 @@ protected function _prepareWebsiteDateTable() * Prepare tier price index table * * @param int|array $entityIds the entity ids limitation - * @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction + * @return AbstractAction */ protected function _prepareTierPriceIndex($entityIds = null) { - $this->tierPriceIndexResource->reindexEntity((array) $entityIds); + $this->tierPriceIndexResource->reindexEntity((array)$entityIds); return $this; } @@ -262,9 +280,9 @@ protected function _prepareTierPriceIndex($entityIds = null) * * @param bool $fullReindexAction * - * @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface[] + * @return PriceInterface[] * - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function getTypeIndexers($fullReindexAction = false) { @@ -301,16 +319,16 @@ public function getTypeIndexers($fullReindexAction = false) * Retrieve Price indexer by Product Type * * @param string $productTypeId - * @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface + * @return PriceInterface * - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\LocalizedException + * @throws InputException + * @throws LocalizedException */ protected function _getIndexer($productTypeId) { $this->getTypeIndexers(); if (!isset($this->_indexers[$productTypeId])) { - throw new \Magento\Framework\Exception\InputException(__('Unsupported product type "%1".', $productTypeId)); + throw new InputException(__('Unsupported product type "%1".', $productTypeId)); } return $this->_indexers[$productTypeId]; } @@ -335,7 +353,7 @@ protected function _insertFromTable($sourceTable, $destTable, $where = null) $select, $destTable, $targetColumns, - \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE + AdapterInterface::INSERT_ON_DUPLICATE ); $this->getConnection()->query($query); } @@ -357,9 +375,9 @@ protected function _emptyTable($table) * @param array $changedIds * @return array Affected ids * - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\LocalizedException - * @throws \Magento\Framework\Exception\NoSuchEntityException + * @throws InputException + * @throws LocalizedException + * @throws NoSuchEntityException */ protected function _reindexRows($changedIds = []) { @@ -407,7 +425,7 @@ protected function _reindexRows($changedIds = []) } /** - * Delete Index data + * Delete Index data index for list of entities * * @param array $entityIds * @return void @@ -418,7 +436,7 @@ private function deleteIndexData(array $entityIds) $select = $this->getConnection()->select()->from( ['index_price' => $this->tableMaintainer->getMainTableByDimensions($dimensions)], null - )->where('index_price.entity_id IN (?)', $entityIds); + )->where('index_price.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE); $query = $select->deleteFromSelect('index_price'); $this->getConnection()->query($query); } @@ -476,7 +494,7 @@ protected function _copyRelationIndexData($parentIds, $excludeIds = null) * * This method is used during both partial and full reindex to identify the table. * - * @param \Magento\Framework\Search\Request\Dimension[] $dimensions + * @param Dimension[] $dimensions * * @return string */ @@ -529,7 +547,7 @@ private function getProductsTypes(array $changedIds = []) ['entity_id', 'type_id'] ); if ($changedIds) { - $select->where('entity_id IN (?)', $changedIds); + $select->where('entity_id IN (?)', $changedIds, \Zend_Db::INT_TYPE); } $pairs = $this->getConnection()->fetchPairs($select); @@ -575,7 +593,7 @@ private function getParentProductsTypes(array $productsIds) /** * Get connection * - * @return \Magento\Framework\DB\Adapter\AdapterInterface + * @return AdapterInterface */ private function getConnection() { diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/AttributeSetFinder.php b/app/code/Magento/Catalog/Model/Product/Attribute/AttributeSetFinder.php index 4df67d1c01f16..b8da7452b09ce 100644 --- a/app/code/Magento/Catalog/Model/Product/Attribute/AttributeSetFinder.php +++ b/app/code/Magento/Catalog/Model/Product/Attribute/AttributeSetFinder.php @@ -27,7 +27,7 @@ public function __construct(CollectionFactory $productCollectionFactory) } /** - * {@inheritdoc} + * @inheritdoc */ public function findAttributeSetIdsByProductIds(array $productIds) { @@ -37,7 +37,7 @@ public function findAttributeSetIdsByProductIds(array $productIds) ->getSelect() ->reset(Select::COLUMNS) ->columns(ProductInterface::ATTRIBUTE_SET_ID) - ->where('entity_id IN (?)', $productIds) + ->where('entity_id IN (?)', $productIds, \Zend_Db::INT_TYPE) ->group(ProductInterface::ATTRIBUTE_SET_ID); $result = $collection->getConnection()->fetchCol($select); return $result; diff --git a/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php b/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php index e3085f7cdefe3..ecab88c9c7e03 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php +++ b/app/code/Magento/Catalog/Model/Product/Price/PricePersistence.php @@ -6,8 +6,16 @@ namespace Magento\Catalog\Model\Product\Price; +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Catalog\Api\ProductAttributeRepositoryInterface; +use Magento\Catalog\Model\ProductIdLocatorInterface; +use Magento\Catalog\Model\ResourceModel\Attribute; +use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Exception\CouldNotDeleteException; +use Magento\Framework\Exception\CouldNotSaveException; + /** - * Price persistence. + * Class responsibly for persistence of prices. */ class PricePersistence { @@ -19,24 +27,24 @@ class PricePersistence private $table = 'catalog_product_entity_decimal'; /** - * @var \Magento\Catalog\Model\ResourceModel\Attribute + * @var Attribute */ private $attributeResource; /** - * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface + * @var ProductAttributeRepositoryInterface */ private $attributeRepository; /** - * @var \Magento\Catalog\Model\ProductIdLocatorInterface + * @var ProductIdLocatorInterface */ private $productIdLocator; /** * Metadata pool. * - * @var \Magento\Framework\EntityManager\MetadataPool + * @var MetadataPool */ private $metadataPool; @@ -64,17 +72,17 @@ class PricePersistence /** * PricePersistence constructor. * - * @param \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource - * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository - * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator - * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool + * @param Attribute $attributeResource + * @param ProductAttributeRepositoryInterface $attributeRepository + * @param ProductIdLocatorInterface $productIdLocator + * @param MetadataPool $metadataPool * @param string $attributeCode */ public function __construct( - \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource, - \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository, - \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator, - \Magento\Framework\EntityManager\MetadataPool $metadataPool, + Attribute $attributeResource, + ProductAttributeRepositoryInterface $attributeRepository, + ProductIdLocatorInterface $productIdLocator, + MetadataPool $metadataPool, $attributeCode = '' ) { $this->attributeResource = $attributeResource; @@ -97,7 +105,7 @@ public function get(array $skus) ->select() ->from($this->attributeResource->getTable($this->table)); return $this->attributeResource->getConnection()->fetchAll( - $select->where($this->getEntityLinkField() . ' IN (?)', $ids) + $select->where($this->getEntityLinkField() . ' IN (?)', $ids, \Zend_Db::INT_TYPE) ->where('attribute_id = ?', $this->getAttributeId()) ); } @@ -107,7 +115,7 @@ public function get(array $skus) * * @param array $prices * @return void - * @throws \Magento\Framework\Exception\CouldNotSaveException + * @throws CouldNotSaveException */ public function update(array $prices) { @@ -127,7 +135,7 @@ public function update(array $prices) $connection->commit(); } catch (\Exception $e) { $connection->rollBack(); - throw new \Magento\Framework\Exception\CouldNotSaveException( + throw new CouldNotSaveException( __('Could not save Prices.'), $e ); @@ -139,7 +147,7 @@ public function update(array $prices) * * @param array $skus * @return void - * @throws \Magento\Framework\Exception\CouldNotDeleteException + * @throws CouldNotDeleteException */ public function delete(array $skus) { @@ -159,7 +167,7 @@ public function delete(array $skus) $connection->commit(); } catch (\Exception $e) { $connection->rollBack(); - throw new \Magento\Framework\Exception\CouldNotDeleteException( + throw new CouldNotDeleteException( __('Could not delete Prices'), $e ); @@ -209,10 +217,10 @@ private function retrieveAffectedIds(array $skus) $affectedIds = []; foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productIds) { - $affectedIds = array_merge($affectedIds, array_keys($productIds)); + $affectedIds[] = array_keys($productIds); } - return array_unique($affectedIds); + return array_unique(array_merge([], ...$affectedIds)); } /** @@ -222,7 +230,7 @@ private function retrieveAffectedIds(array $skus) */ public function getEntityLinkField() { - return $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class) + return $this->metadataPool->getMetadata(ProductInterface::class) ->getLinkField(); } } diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php b/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php index fd247d2ce9e32..65b1aec3b4817 100644 --- a/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php +++ b/app/code/Magento/Catalog/Model/Product/Price/TierPricePersistence.php @@ -56,7 +56,7 @@ public function get(array $ids) { $select = $this->tierpriceResource->getConnection()->select()->from($this->tierpriceResource->getMainTable()); return $this->tierpriceResource->getConnection()->fetchAll( - $select->where($this->getEntityLinkField() . ' IN (?)', $ids) + $select->where($this->getEntityLinkField() . ' IN (?)', $ids, \Zend_Db::INT_TYPE) ); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php index 3946be32184ec..c71225b4fc67f 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php @@ -147,7 +147,7 @@ protected function _getLoadAttributesSelect($object, $table) ->select() ->from(['attr_table' => $table], []) ->where("attr_table.{$this->getLinkField()} = ?", $object->getData($this->getLinkField())) - ->where('attr_table.store_id IN (?)', $storeIds); + ->where('attr_table.store_id IN (?)', $storeIds, \Zend_Db::INT_TYPE); if ($setId) { $select->join( @@ -562,7 +562,11 @@ public function getAttributeRawValue($entityId, $attribute, $store) if ($typedAttributes) { foreach ($typedAttributes as $table => $_attributes) { $defaultJoinCondition = [ - $connection->quoteInto('default_value.attribute_id IN (?)', array_keys($_attributes)), + $connection->quoteInto( + 'default_value.attribute_id IN (?)', + array_keys($_attributes), + \Zend_Db::INT_TYPE + ), "default_value.{$this->getLinkField()} = e.{$this->getLinkField()}", 'default_value.store_id = 0', ]; @@ -589,7 +593,11 @@ public function getAttributeRawValue($entityId, $attribute, $store) 'store_value.attribute_id' ); $joinCondition = [ - $connection->quoteInto('store_value.attribute_id IN (?)', array_keys($_attributes)), + $connection->quoteInto( + 'store_value.attribute_id IN (?)', + array_keys($_attributes), + \Zend_Db::INT_TYPE + ), "store_value.{$this->getLinkField()} = e.{$this->getLinkField()}", 'store_value.store_id = :store_id', ]; diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php index 298ca059c572e..917aafb643b47 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php @@ -13,13 +13,13 @@ namespace Magento\Catalog\Model\ResourceModel; +use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Model\Indexer\Category\Product\Processor; +use Magento\Catalog\Setup\CategorySetup; use Magento\Framework\App\ObjectManager; use Magento\Framework\DataObject; use Magento\Framework\EntityManager\EntityManager; -use Magento\Catalog\Setup\CategorySetup; use Magento\Framework\EntityManager\MetadataPool; -use Magento\Catalog\Api\Data\ProductInterface; /** * Resource model for category entity @@ -666,7 +666,8 @@ public function findWhereAttributeIs($entityIdsFilter, $attribute, $expectedValu 'ci.value = :value' )->where( 'ce.entity_id IN (?)', - $entityIdsFilter + $entityIdsFilter, + \Zend_Db::INT_TYPE ); $this->entitiesWhereAttributesIs[$entityIdsFilterHash][$attribute->getId()][$expectedValue] = $this->getConnection()->fetchCol($selectEntities, $bind); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php index cd1b8c8924552..759866de4b49d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Flat.php @@ -294,7 +294,7 @@ protected function _loadNodes($parentNode = null, $recursionLevel = 0, $storeId $inactiveCategories = $this->getInactiveCategoryIds(); if (!empty($inactiveCategories)) { - $select->where('main_table.entity_id NOT IN (?)', $inactiveCategories); + $select->where('main_table.entity_id NOT IN (?)', $inactiveCategories, \Zend_Db::INT_TYPE); } // Allow extensions to modify select (e.g. add custom category attributes to select) @@ -681,7 +681,8 @@ public function getAnchorsAbove(array $filterIds, $storeId = 0) 1 )->where( 'entity_id IN (?)', - $filterIds + $filterIds, + \Zend_Db::INT_TYPE ); return $this->getConnection()->fetchCol($select); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php b/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php index 3a0d47fe573fb..02fdb8270791d 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php @@ -170,10 +170,12 @@ protected function _getLoadAttributesSelect($table, $attributeIds = []) ['e.entity_id'] )->where( "e.entity_id IN (?)", - array_keys($this->_itemsById) + array_keys($this->_itemsById), + \Zend_Db::INT_TYPE )->where( 't_d.attribute_id IN (?)', - $attributeIds + $attributeIds, + \Zend_Db::INT_TYPE )->joinLeft( ['t_s' => $table], implode(' AND ', $joinCondition), @@ -192,10 +194,12 @@ protected function _getLoadAttributesSelect($table, $attributeIds = []) ['e.entity_id'] )->where( "e.entity_id IN (?)", - array_keys($this->_itemsById) + array_keys($this->_itemsById), + \Zend_Db::INT_TYPE )->where( 'attribute_id IN (?)', - $attributeIds + $attributeIds, + \Zend_Db::INT_TYPE )->where( 'store_id = ?', $this->getDefaultStoreId() diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php index c3f1ee28b19fe..b174e4beb6353 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php @@ -227,12 +227,16 @@ public function getWebsiteIds($product) */ public function getWebsiteIdsByProductIds($productIds) { + if (!is_array($productIds) || empty($productIds)) { + return []; + } $select = $this->getConnection()->select()->from( $this->getProductWebsiteTable(), ['product_id', 'website_id'] )->where( 'product_id IN (?)', - $productIds + $productIds, + \Zend_Db::INT_TYPE ); $productsWebsites = []; foreach ($this->getConnection()->fetchAll($select) as $productInfo) { @@ -357,7 +361,7 @@ private function deleteSelectedEntityAttributeRows(DataObject $product, array $a $entityId = $product->getData($entityIdField); foreach ($backendTables as $backendTable => $attributes) { $connection = $this->getConnection(); - $where = $connection->quoteInto('attribute_id IN (?)', $attributes); + $where = $connection->quoteInto('attribute_id IN (?)', $attributes, \Zend_Db::INT_TYPE); $where .= $connection->quoteInto(" AND {$entityIdField} = ?", $entityId); $connection->delete($backendTable, $where); } @@ -450,6 +454,7 @@ public function getAvailableInCategories($object) // fetching all parent IDs, including those are higher on the tree $entityId = (int)$object->getEntityId(); if (!isset($this->availableCategoryIdsCache[$entityId])) { + $unionTables = []; foreach ($this->_storeManager->getStores() as $store) { $unionTables[] = $this->getAvailableInCategoriesSelect( $entityId, @@ -594,7 +599,8 @@ public function getProductsSku(array $productIds) ['entity_id', 'sku'] )->where( 'entity_id IN (?)', - $productIds + $productIds, + \Zend_Db::INT_TYPE ); return $this->getConnection()->fetchAll($select); } diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php index 0cc3090100e8b..7dbfe0d5fccea 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php @@ -858,7 +858,8 @@ protected function doAddWebsiteNamesToResult() ['name'] )->where( 'product_website.product_id IN (?)', - array_keys($productWebsites) + array_keys($productWebsites), + \Zend_Db::INT_TYPE )->where( 'website.website_id > ?', 0 @@ -1358,7 +1359,7 @@ public function addCountToCategories($categoryCollection) $anchorStmt = clone $select; $anchorStmt->limit(); //reset limits - $anchorStmt->where('count_table.category_id IN (?)', $isAnchor); + $anchorStmt->where('count_table.category_id IN (?)', $isAnchor, \Zend_Db::INT_TYPE); $productCounts += $this->getConnection()->fetchPairs($anchorStmt); $anchorStmt = null; } @@ -1366,7 +1367,7 @@ public function addCountToCategories($categoryCollection) $notAnchorStmt = clone $select; $notAnchorStmt->limit(); //reset limits - $notAnchorStmt->where('count_table.category_id IN (?)', $isNotAnchor); + $notAnchorStmt->where('count_table.category_id IN (?)', $isNotAnchor, \Zend_Db::INT_TYPE); $notAnchorStmt->where('count_table.is_parent = 1'); $productCounts += $this->getConnection()->fetchPairs($notAnchorStmt); $notAnchorStmt = null; @@ -2165,7 +2166,7 @@ public function addCategoryIds() $select = $this->getConnection()->select(); $select->from($this->_productCategoryTable, ['product_id', 'category_id']); - $select->where('product_id IN (?)', $ids); + $select->where('product_id IN (?)', $ids, \Zend_Db::INT_TYPE); $data = $this->getConnection()->fetchAll($select); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php index a9741cd8e1ec7..ef274b1bef55e 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Gallery.php @@ -487,7 +487,8 @@ public function getProductImages($product, $storeIds) $product->getData($this->metadata->getLinkField()) )->where( 'store_id IN (?)', - $storeIds + $storeIds, + \Zend_Db::INT_TYPE )->where( 'attribute_code IN (?)', ['small_image', 'thumbnail', 'image'] diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php index 747b06266cce0..578e3099a2fde 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php @@ -775,7 +775,7 @@ protected function _movePriceDataToIndexTable($entityIds = null) $select = $connection->select()->from($table, $columns); if ($entityIds !== null) { - $select->where('entity_id in (?)', count($entityIds) > 0 ? $entityIds : 0); + $select->where('entity_id in (?)', count($entityIds) > 0 ? $entityIds : 0, \Zend_Db::INT_TYPE); } $query = $select->insertFromSelect($this->getIdxTable(), [], false); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/TierPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/TierPrice.php index a866c1eaa413f..aa66978fa0036 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/TierPrice.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/TierPrice.php @@ -193,7 +193,7 @@ private function getTierPriceSelect(bool $isAllWebsites, bool $isAllCustomerGrou [] ); if (!empty($entityIds)) { - $select->where('entity.entity_id IN (?)', $entityIds); + $select->where('entity.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE); } $this->joinWebsites($select, $isAllWebsites); $this->joinCustomerGroups($select, $isAllCustomerGroups); diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/Product/Collection.php index f1d4552cf37f0..bca919e700364 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/Product/Collection.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Link/Product/Collection.php @@ -6,12 +6,32 @@ namespace Magento\Catalog\Model\ResourceModel\Product\Link\Product; use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; +use Magento\Catalog\Model\Indexer\Product\Flat\State; use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver; +use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Link as LinkModel; +use Magento\Catalog\Model\Product\OptionFactory; use Magento\Catalog\Model\ResourceModel\Category; +use Magento\Catalog\Model\ResourceModel\Helper; use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory; +use Magento\Catalog\Model\ResourceModel\Url; use Magento\Customer\Api\GroupManagementInterface; +use Magento\Customer\Model\Session; +use Magento\Eav\Model\Config; +use Magento\Eav\Model\EntityFactory; +use Magento\Framework\App\Config\ScopeConfigInterface; +use Magento\Framework\App\ResourceConnection; +use Magento\Framework\Data\Collection\Db\FetchStrategyInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; use Magento\Framework\EntityManager\MetadataPool; +use Magento\Framework\Event\ManagerInterface; use Magento\Framework\Indexer\DimensionFactory; +use Magento\Framework\Module\Manager; +use Magento\Framework\Stdlib\DateTime; +use Magento\Framework\Stdlib\DateTime\TimezoneInterface; +use Magento\Framework\Validator\UniversalFactory; +use Magento\Store\Model\StoreManagerInterface; +use Psr\Log\LoggerInterface; /** * Catalog product linked products collection @@ -26,14 +46,14 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection /** * Store product model * - * @var \Magento\Catalog\Model\Product + * @var Product */ protected $_product; /** * Store product link model * - * @var \Magento\Catalog\Model\Product\Link + * @var LinkModel */ protected $_linkModel; @@ -71,25 +91,25 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection /** * Collection constructor. * @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory - * @param \Psr\Log\LoggerInterface $logger - * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy - * @param \Magento\Framework\Event\ManagerInterface $eventManager - * @param \Magento\Eav\Model\Config $eavConfig - * @param \Magento\Framework\App\ResourceConnection $resource - * @param \Magento\Eav\Model\EntityFactory $eavEntityFactory - * @param \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper - * @param \Magento\Framework\Validator\UniversalFactory $universalFactory - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Framework\Module\Manager $moduleManager - * @param \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState - * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig - * @param \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory - * @param \Magento\Catalog\Model\ResourceModel\Url $catalogUrl - * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate - * @param \Magento\Customer\Model\Session $customerSession - * @param \Magento\Framework\Stdlib\DateTime $dateTime + * @param LoggerInterface $logger + * @param FetchStrategyInterface $fetchStrategy + * @param ManagerInterface $eventManager + * @param Config $eavConfig + * @param ResourceConnection $resource + * @param EntityFactory $eavEntityFactory + * @param Helper $resourceHelper + * @param UniversalFactory $universalFactory + * @param StoreManagerInterface $storeManager + * @param Manager $moduleManager + * @param State $catalogProductFlatState + * @param ScopeConfigInterface $scopeConfig + * @param OptionFactory $productOptionFactory + * @param Url $catalogUrl + * @param TimezoneInterface $localeDate + * @param Session $customerSession + * @param DateTime $dateTime * @param GroupManagementInterface $groupManagement - * @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection + * @param AdapterInterface|null $connection * @param ProductLimitationFactory|null $productLimitationFactory * @param MetadataPool|null $metadataPool * @param TableMaintainer|null $tableMaintainer @@ -101,25 +121,25 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection */ public function __construct( \Magento\Framework\Data\Collection\EntityFactory $entityFactory, - \Psr\Log\LoggerInterface $logger, - \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, - \Magento\Framework\Event\ManagerInterface $eventManager, - \Magento\Eav\Model\Config $eavConfig, - \Magento\Framework\App\ResourceConnection $resource, - \Magento\Eav\Model\EntityFactory $eavEntityFactory, - \Magento\Catalog\Model\ResourceModel\Helper $resourceHelper, - \Magento\Framework\Validator\UniversalFactory $universalFactory, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Framework\Module\Manager $moduleManager, - \Magento\Catalog\Model\Indexer\Product\Flat\State $catalogProductFlatState, - \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig, - \Magento\Catalog\Model\Product\OptionFactory $productOptionFactory, - \Magento\Catalog\Model\ResourceModel\Url $catalogUrl, - \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate, - \Magento\Customer\Model\Session $customerSession, - \Magento\Framework\Stdlib\DateTime $dateTime, + LoggerInterface $logger, + FetchStrategyInterface $fetchStrategy, + ManagerInterface $eventManager, + Config $eavConfig, + ResourceConnection $resource, + EntityFactory $eavEntityFactory, + Helper $resourceHelper, + UniversalFactory $universalFactory, + StoreManagerInterface $storeManager, + Manager $moduleManager, + State $catalogProductFlatState, + ScopeConfigInterface $scopeConfig, + OptionFactory $productOptionFactory, + Url $catalogUrl, + TimezoneInterface $localeDate, + Session $customerSession, + DateTime $dateTime, GroupManagementInterface $groupManagement, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, + AdapterInterface $connection = null, ProductLimitationFactory $productLimitationFactory = null, MetadataPool $metadataPool = null, TableMaintainer $tableMaintainer = null, @@ -166,10 +186,10 @@ public function __construct( /** * Declare link model and initialize type attributes join * - * @param \Magento\Catalog\Model\Product\Link $linkModel + * @param LinkModel $linkModel * @return $this */ - public function setLinkModel(\Magento\Catalog\Model\Product\Link $linkModel) + public function setLinkModel(LinkModel $linkModel) { $this->_linkModel = $linkModel; if ($linkModel->getLinkTypeId()) { @@ -202,10 +222,10 @@ public function getLinkModel() /** * Initialize collection parent product and add limitation join * - * @param \Magento\Catalog\Model\Product $product + * @param Product $product * @return $this */ - public function setProduct(\Magento\Catalog\Model\Product $product) + public function setProduct(Product $product) { $this->_product = $product; if ($product && $product->getId()) { @@ -239,7 +259,11 @@ public function addExcludeProductFilter($products) $products = [$products]; } $this->_hasLinkFilter = true; - $this->getSelect()->where('links.linked_product_id NOT IN (?)', $products); + $this->getSelect()->where( + 'links.linked_product_id NOT IN (?)', + $products, + \Zend_Db::INT_TYPE + ); } return $this; } @@ -257,7 +281,11 @@ public function addProductFilter($products) $products = [$products]; } $identifierField = $this->getLinkField(); - $this->getSelect()->where("product_entity_table.$identifierField IN (?)", $products); + $this->getSelect()->where( + "product_entity_table.$identifierField IN (?)", + $products, + \Zend_Db::INT_TYPE + ); $this->_hasLinkFilter = true; } @@ -319,10 +347,18 @@ protected function _joinLinks() $linkField = $this->getLinkField(); if ($this->productIds) { if ($this->_isStrongMode) { - $this->getSelect()->where('links.product_id in (?)', $this->productIds); + $this->getSelect()->where( + 'links.product_id in (?)', + $this->productIds, + \Zend_Db::INT_TYPE + ); } else { $joinType = 'joinLeft'; - $joinCondition[] = $connection->quoteInto('links.product_id in (?)', $this->productIds); + $joinCondition[] = $connection->quoteInto( + 'links.product_id in (?)', + $this->productIds, + \Zend_Db::INT_TYPE + ); } if (count($this->productIds) === 1) { $this->addFieldToFilter( diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Website.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Website.php index 771f781678e44..eee5106579255 100644 --- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Website.php +++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Website.php @@ -125,7 +125,8 @@ public function getWebsites($productIds) ['product_id', 'website_id'] )->where( 'product_id IN (?)', - $productIds + $productIds, + \Zend_Db::INT_TYPE ); $rowset = $this->getConnection()->fetchAll($select); diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartButtonActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartButtonActionGroup.xml new file mode 100644 index 0000000000000..3d240a21afc28 --- /dev/null +++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontClickAddToCartButtonActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontClickAddToCartButtonActionGroup"> + <annotations> + <description>Click "Add to Cart" button.</description> + </annotations> + <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <waitForPageLoad stepKey="waitAddToCart"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml index c35e775152ac9..c94bca1ca5c13 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml @@ -12,7 +12,7 @@ <element name="collapseAll" type="button" selector=".tree-actions a:first-child"/> <element name="expandAll" type="button" selector=".tree-actions a:last-child"/> <element name="categoryHighlighted" type="text" selector="//div[@id='store.menu']//span[contains(text(),'{{name}}')]/ancestor::li" parameterized="true" timeout="30"/> - <element name="categoryNotHighlighted" type="text" selector="ul[id=\'ui-id-2\'] li[class~=\'active\']" timeout="30"/> + <element name="categoryNotHighlighted" type="text" selector="[id=\'store.menu\'] ul li.active" timeout="30"/> <element name="categoryTreeRoot" type="text" selector="div.x-tree-root-node>li.x-tree-node:first-of-type>div.x-tree-node-el:first-of-type" timeout="30"/> <element name="categoryInTree" type="text" selector="//a/span[contains(text(), '{{name}}')]" parameterized="true" timeout="30"/> <element name="categoryInTreeUnderRoot" type="text" selector="//li/ul/li[@class='x-tree-node']/div/a/span[contains(text(), '{{name}}')]" parameterized="true"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml index 4e86f14611c24..201affacd9adb 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml @@ -38,5 +38,6 @@ <element name="storeViewDropdown" type="text" selector="//select[@name='store_id']/option[contains(.,'{{storeView}}')]" parameterized="true"/> <element name="inputByCodeRangeFrom" type="input" selector="input.admin__control-text[name='{{code}}[from]']" parameterized="true"/> <element name="inputByCodeRangeTo" type="input" selector="input.admin__control-text[name='{{code}}[to]']" parameterized="true"/> + <element name="storeViewOptions" type="text" selector=".admin__data-grid-outer-wrap select[name='store_id'] > option[value='{{value}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontFooterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontFooterSection.xml index 1c937637ad823..a7dd622c56a7f 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontFooterSection.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontFooterSection.xml @@ -9,6 +9,7 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="StorefrontFooterSection"> <element name="switchStoreButton" type="button" selector="#switcher-store-trigger"/> + <element name="storeViewOptionNumber" type="button" selector="//div[@class='actions dropdown options switcher-options active']//ul//li[{{var1}}]//a" parameterized="true"/> <element name="storeLink" type="button" selector="//ul[@class='dropdown switcher-dropdown']//a[contains(text(),'{{var1}}')]" parameterized="true" timeout="30"/> </section> </sections> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest/AdminConfigDefaultCategoryLayoutFromConfigurationSettingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest/AdminConfigDefaultCategoryLayoutFromConfigurationSettingTest.xml index 900b3f6cd2f1c..852353300d090 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest/AdminConfigDefaultCategoryLayoutFromConfigurationSettingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryTest/AdminConfigDefaultCategoryLayoutFromConfigurationSettingTest.xml @@ -24,8 +24,7 @@ <actionGroup ref="RestoreLayoutSetting" stepKey="sampleActionGroup"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{WebConfigurationPage.url}}" stepKey="navigateToWebConfigurationPage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminOpenWebConfigurationPageActionGroup" stepKey="navigateToWebConfigurationPage"/> <conditionalClick stepKey="expandDefaultLayouts" selector="{{WebSection.DefaultLayoutsTab}}" dependentSelector="{{WebSection.CheckIfTabExpand}}" visible="true"/> <waitForElementVisible selector="{{DefaultLayoutsSection.categoryLayout}}" stepKey="waitForDefaultCategoryLayout"/> <seeOptionIsSelected selector="{{DefaultLayoutsSection.categoryLayout}}" userInput="No layout updates" stepKey="seeNoLayoutUpdatesSelected"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml index dfa289f18711b..500c95d1120f3 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml @@ -76,8 +76,7 @@ <argument name="tags" value=""/> </actionGroup> <!--Open Index Management Page --> - <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> - <waitForPageLoad stepKey="waitForIndexPageToBeLoaded"/> + <actionGroup ref="AdminOpenIndexManagementPageActionGroup" stepKey="openIndexManagementPage"/> <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> <!--Verify Category In Store Front--> <amOnPage url="/$$createCategory.name$$.html" stepKey="openCategoryPage1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml index d0c40ec276abb..2394b41502f84 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml @@ -76,9 +76,7 @@ <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> <argument name="tags" value=""/> </actionGroup> - <!--Open Index Management Page --> - <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> - <waitForPageLoad stepKey="waitForIndexPageToBeLoaded"/> + <actionGroup ref="AdminOpenIndexManagementPageActionGroup" stepKey="openIndexManagementPage"/> <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> <!--Verify Category In Store Front--> <amOnPage url="/$$createCategory.name$$.html" stepKey="openCategoryPage1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml index 8256661f8c525..35e53273aebf2 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml @@ -77,9 +77,7 @@ <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> <argument name="tags" value=""/> </actionGroup> - <!--Open Index Management Page --> - <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> - <waitForPageLoad stepKey="waitForIndexPageToBeLoaded"/> + <actionGroup ref="AdminOpenIndexManagementPageActionGroup" stepKey="openIndexManagementPage"/> <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> <!--Verify Category In Store Front--> <amOnPage url="/$$category.name$$.html" stepKey="openCategoryPage1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminConfigDefaultProductLayoutFromConfigurationSettingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminConfigDefaultProductLayoutFromConfigurationSettingTest.xml index 7317f2f7214f0..ac2e86a572455 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminConfigDefaultProductLayoutFromConfigurationSettingTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminConfigDefaultProductLayoutFromConfigurationSettingTest.xml @@ -24,8 +24,7 @@ <actionGroup ref="RestoreLayoutSetting" stepKey="sampleActionGroup"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{WebConfigurationPage.url}}" stepKey="navigateToWebConfigurationPage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminOpenWebConfigurationPageActionGroup" stepKey="navigateToWebConfigurationPage"/> <conditionalClick stepKey="expandDefaultLayouts" selector="{{WebSection.DefaultLayoutsTab}}" dependentSelector="{{WebSection.CheckIfTabExpand}}" visible="true"/> <waitForElementVisible selector="{{DefaultLayoutsSection.productLayout}}" stepKey="DefaultProductLayout"/> <selectOption selector="{{DefaultLayoutsSection.productLayout}}" userInput="3 columns" stepKey="select3ColumnsLayout"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml index 059a3a321b16a..ac9c0206f4e24 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml @@ -25,8 +25,7 @@ <createData entity="_defaultCategory" stepKey="createSecondCategory"/> <!-- Switch "Category Product" and "Product Category" indexers to "Update by Schedule" mode --> - <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="onIndexManagement"/> - <waitForPageLoad stepKey="waitForManagementPage"/> + <actionGroup ref="AdminOpenIndexManagementPageActionGroup" stepKey="onIndexManagement"/> <actionGroup ref="AdminSwitchIndexerToActionModeActionGroup" stepKey="switchCategoryProduct"> <argument name="indexerValue" value="catalog_category_product"/> @@ -38,8 +37,7 @@ <after> <!-- Switch "Category Product" and "Product Category" indexers to "Update by Save" mode --> - <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="onIndexManagement"/> - <waitForPageLoad stepKey="waitForManagementPage"/> + <actionGroup ref="AdminOpenIndexManagementPageActionGroup" stepKey="onIndexManagement"/> <actionGroup ref="AdminSwitchIndexerToActionModeActionGroup" stepKey="switchCategoryProduct"> <argument name="indexerValue" value="catalog_category_product"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridUrlFilterApplierTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridUrlFilterApplierTest.xml index fea4436446da2..2eda7b8d02481 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridUrlFilterApplierTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridUrlFilterApplierTest.xml @@ -31,11 +31,11 @@ <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutOfAdmin"/> </after> - <amOnPage url="{{AdminProductIndexPage.url}}?filters[name]=$createSimpleProduct.name$" stepKey="navigateToProductGridWithFilters"/> + <amOnPage url="{{AdminProductIndexPage.url}}?filters[sku]=$createSimpleProduct.sku$" stepKey="navigateToProductGridWithFilters"/> <waitForPageLoad stepKey="waitForProductGrid"/> <see selector="{{AdminProductGridSection.productGridNameProduct($createSimpleProduct.name$)}}" userInput="$createSimpleProduct.name$" stepKey="seeProduct"/> <waitForElementVisible selector="{{AdminProductGridFilterSection.enabledFilters}}" stepKey="waitForEnabledFilters"/> <seeElement selector="{{AdminProductGridFilterSection.enabledFilters}}" stepKey="seeEnabledFilters"/> - <see selector="{{AdminProductGridFilterSection.enabledFilters}}" userInput="Name: $createSimpleProduct.name$" stepKey="seeProductNameFilter"/> + <see selector="{{AdminProductGridFilterSection.enabledFilters}}" userInput="SKU: $createSimpleProduct.sku$" stepKey="seeProductNameFilter"/> </test> </tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml index b6c508df121df..208b588493112 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml @@ -89,9 +89,7 @@ <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> <argument name="tags" value=""/> </actionGroup> - <!--Open Index Management Page --> - <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> - <waitForPageLoad stepKey="waitForIndexPageToLoad"/> + <actionGroup ref="AdminOpenIndexManagementPageActionGroup" stepKey="openIndexManagementPage"/> <see stepKey="seeCategoryIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> <!--Verify Product In Store Front--> <amOnPage url="$$createSimpleProduct.name$$.html" stepKey="goToStorefrontPage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml index 8eb7813b6203e..a688dea47a0c4 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml @@ -71,9 +71,7 @@ <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> <argument name="tags" value=""/> </actionGroup> - <!--Open Index Management Page --> - <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> - <waitForPageLoad stepKey="waitForIndexPageToBeLoaded"/> + <actionGroup ref="AdminOpenIndexManagementPageActionGroup" stepKey="openIndexManagementPage"/> <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="Ready"/> <!--Verify Category In Store Front--> <amOnPage url="/$$createCategory.name$$.html" stepKey="openCategoryPage1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml index 94f8d0e1dc523..27a834833ed76 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml @@ -76,9 +76,7 @@ <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> <argument name="tags" value=""/> </actionGroup> - <!--Open Index Management Page --> - <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> - <waitForPageLoad stepKey="waitForIndexPageToLoad"/> + <actionGroup ref="AdminOpenIndexManagementPageActionGroup" stepKey="openIndexManagementPage"/> <see stepKey="seeIndexStatus" selector="{{AdminIndexManagementSection.indexerStatus('Category Flat Data')}}" userInput="READY"/> <!--Verify Category In Store Front--> <amOnPage url="{{SimpleSubCategory.name}}.html" stepKey="goToStorefrontPage"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyComparedAtWebsiteLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyComparedAtWebsiteLevelTest.xml deleted file mode 100644 index 7ec5fea49f64b..0000000000000 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyComparedAtWebsiteLevelTest.xml +++ /dev/null @@ -1,108 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StoreFrontRecentlyComparedAtWebsiteLevelTest"> - <annotations> - <features value="Catalog"/> - <stories value="Recently Compared Product"/> - <title value="Recently Compared Product at website level"/> - <description value="Recently Compared Products widget appears on a page immediately after adding product to compare"/> - <severity value="MAJOR"/> - <testCaseId value="MC-33099"/> - <useCaseId value="MC-32763"/> - <group value="catalog"/> - <group value="widget"/> - <skip> - <issueId value="MC-34091"/> - </skip> - </annotations> - <before> - <!-- Set Stores > Configurations > Catalog > Recently Viewed/Compared Products > Show for Current = Website --> - <magentoCLI command="config:set {{RecentlyViewedProductScopeWebsite.path}} {{RecentlyViewedProductScopeWebsite.value}}" stepKey="setRecentlyViewedComparedProductsScopeToWebsite"/> - <!--Create Simple Products and Category --> - <createData entity="SimpleSubCategory" stepKey="createCategory"/> - <createData entity="SimpleProduct" stepKey="createSimpleProductToCompareFirst"> - <requiredEntity createDataKey="createCategory"/> - </createData> - <createData entity="SimpleProduct" stepKey="createSimpleProductToCompareSecond"> - <requiredEntity createDataKey="createCategory"/> - </createData> - <createData entity="SimpleProduct" stepKey="createSimpleProductNotVisibleFirst"> - <requiredEntity createDataKey="createCategory"/> - </createData> - <createData entity="SimpleProduct" stepKey="createSimpleProductNotVisibleSecond"> - <requiredEntity createDataKey="createCategory"/> - </createData> - <createData entity="Simple_US_Customer" stepKey="createCustomer"/> - <!-- Login as admin --> - <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <!-- Create product widget --> - <actionGroup ref="AdminCreateRecentlyProductsWidgetActionGroup" stepKey="createRecentlyComparedProductsWidget"> - <argument name="widget" value="RecentlyComparedProductsWidget"/> - </actionGroup> - </before> - <after> - <!-- Reset Stores > Configurations > Catalog > Recently Viewed/Compared Products > Show for Current = Website--> - <magentoCLI command="config:set {{RecentlyViewedProductScopeWebsite.path}} {{RecentlyViewedProductScopeWebsite.value}}" stepKey="setRecentlyViewedComparedProductsScopeToDefault"/> - <!-- Delete Products and Category --> - <deleteData createDataKey="createSimpleProductToCompareFirst" stepKey="deleteSimpleProductToCompareFirst"/> - <deleteData createDataKey="createSimpleProductToCompareSecond" stepKey="deleteSimpleProductToCompareSecond"/> - <deleteData createDataKey="createSimpleProductNotVisibleFirst" stepKey="deleteSimpleProductNotVisibleFirst"/> - <deleteData createDataKey="createSimpleProductNotVisibleSecond" stepKey="deleteSimpleProductNotVisibleSecond"/> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <!-- Customer Logout --> - <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="logoutFromCustomer"/> - <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> - <!-- Delete product widget --> - <actionGroup ref="AdminDeleteWidgetActionGroup" stepKey="deleteRecentlyComparedProductsWidget"> - <argument name="widget" value="RecentlyComparedProductsWidget"/> - </actionGroup> - <!-- Logout Admin --> - <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> - </after> - <!--Login to storefront from customer--> - <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginCustomer"> - <argument name="Customer" value="$createCustomer$"/> - </actionGroup> - <see userInput="Welcome, $createCustomer.firstname$ $createCustomer.lastname$!" selector="{{StorefrontPanelHeaderSection.welcomeMessage}}" stepKey="checkWelcomeMessage"/> - <amOnPage url="{{StorefrontCategoryPage.url($createCategory.custom_attributes[url_key]$)}}" stepKey="openCategoryPage"/> - <!--Add to compare Simple Product and Simple Product 2--> - <actionGroup ref="StorefrontAddCategoryProductToCompareActionGroup" stepKey="addSimpleProduct1ToCompare" > - <argument name="productVar" value="$createSimpleProductToCompareFirst$"/> - </actionGroup> - <actionGroup ref="StorefrontAddCategoryProductToCompareActionGroup" stepKey="addSimpleProduct2ToCompare" > - <argument name="productVar" value="$createSimpleProductToCompareSecond$"/> - </actionGroup> - <!--The Compare Products widget displays Simple Product 1 and Simple Product 2--> - <actionGroup ref="StorefrontCheckCompareSidebarProductActionGroup" stepKey="checkSimpleProduct1InCompareSidebar"> - <argument name="productVar" value="$createSimpleProductToCompareFirst$"/> - </actionGroup> - <actionGroup ref="StorefrontCheckCompareSidebarProductActionGroup" stepKey="checkSimpleProduct2InCompareSidebar"> - <argument name="productVar" value="$createSimpleProductToCompareSecond$"/> - </actionGroup> - - <!--Click Clear all in the Compare Products widget--> - <actionGroup ref="StorefrontClearCompareActionGroup" stepKey="clearCompareList"/> - <!--The Recently Compared widget displays Simple Product 1 and Simple Product 2--> - <waitForPageLoad stepKey="waitForRecentlyComparedWidgetLoad"/> - <actionGroup ref="StorefrontAssertProductInRecentlyComparedWidgetActionGroup" stepKey="checkSimpleProduct1ExistInRecentlyComparedWidget"> - <argument name="product" value="$createSimpleProductToCompareFirst$"/> - </actionGroup> - <actionGroup ref="StorefrontAssertProductInRecentlyComparedWidgetActionGroup" stepKey="checkSimpleProduct2ExistInRecentlyComparedWidget"> - <argument name="product" value="$createSimpleProductToCompareSecond$"/> - </actionGroup> - <!--The Recently Compared widget not displays Simple Product 3 and Simple Product 4--> - <actionGroup ref="StorefrontAssertNotExistProductInRecentlyComparedWidgetActionGroup" stepKey="checkSimpleProduct3NotExistInRecentlyComparedWidget"> - <argument name="product" value="$createSimpleProductNotVisibleFirst$"/> - </actionGroup> - <actionGroup ref="StorefrontAssertNotExistProductInRecentlyComparedWidgetActionGroup" stepKey="checkSimpleProduct4NotExistInRecentlyComparedWidget"> - <argument name="product" value="$createSimpleProductNotVisibleSecond$"/> - </actionGroup> - </test> -</tests> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml index 74264149cf1cb..9731b66209df0 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml @@ -34,6 +34,9 @@ <deleteData createDataKey="category3" stepKey="deleteCategory3"/> <deleteData createDataKey="category2" stepKey="deleteCategory2"/> <deleteData createDataKey="category1" stepKey="deleteCategory1"/> + <actionGroup ref="CliCacheFlushActionGroup" stepKey="cleanInvalidatedCaches"> + <argument name="tags" value="full_page"/> + </actionGroup> </after> <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="amOnStorefrontPage"/> <moveMouseOver diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml index e768f7ba5317a..631d1d50077e9 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml @@ -152,7 +152,7 @@ <click selector="{{AdminOrderDetailsMainActionsSection.reorder}}" stepKey="clickReorder"/> <actionGroup ref="AdminCheckoutSelectCheckMoneyOrderBillingMethodActionGroup" stepKey="selectBillingMethod"/> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="trySubmitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="trySubmitOrder" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionField.title}}" stepKey="seeAdminOrderProductOptionField1" /> <see selector="{{AdminOrderItemsOrderedSection.productNameOptions}}" userInput="{{ProductOptionArea.title}}" stepKey="seeAdminOrderProductOptionArea1"/> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml index 0dccc409a1032..164701fa5bc6d 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml @@ -37,16 +37,14 @@ </after> <!--Set timezone for default config--> - <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfig"/> - <waitForPageLoad stepKey="waitForConfigPage"/> + <actionGroup ref="AdminOpenGeneralConfigurationPageActionGroup" stepKey="goToGeneralConfig"/> <conditionalClick selector="{{LocaleOptionsSection.sectionHeader}}" dependentSelector="{{LocaleOptionsSection.timezone}}" visible="false" stepKey="openLocaleSection"/> <grabValueFrom selector="{{LocaleOptionsSection.timezone}}" stepKey="originalTimezone"/> <selectOption selector="{{LocaleOptionsSection.timezone}}" userInput="Central European Standard Time (Europe/Paris)" stepKey="setTimezone"/> <click selector="{{AdminMainActionsSection.save}}" stepKey="saveConfig"/> <!--Set timezone for Main Website--> - <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfig1"/> - <waitForPageLoad stepKey="waitForConfigPage1"/> + <actionGroup ref="AdminOpenGeneralConfigurationPageActionGroup" stepKey="goToGeneralConfig1"/> <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="AdminSwitchStoreViewActionGroup"> <argument name="website" value="_defaultWebsite"/> </actionGroup> @@ -80,15 +78,13 @@ </assertEquals> <!--Reset timezone--> - <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfigReset"/> - <waitForPageLoad stepKey="waitForConfigPageReset"/> + <actionGroup ref="AdminOpenGeneralConfigurationPageActionGroup" stepKey="goToGeneralConfigReset"/> <conditionalClick selector="{{LocaleOptionsSection.sectionHeader}}" dependentSelector="{{LocaleOptionsSection.timezone}}" visible="false" stepKey="openLocaleSectionReset"/> <selectOption selector="{{LocaleOptionsSection.timezone}}" userInput="$originalTimezone" stepKey="resetTimezone"/> <click selector="{{AdminMainActionsSection.save}}" stepKey="saveConfigReset"/> <!--Reset timezone--> - <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfigReset1"/> - <waitForPageLoad stepKey="waitForConfigPageReset1"/> + <actionGroup ref="AdminOpenGeneralConfigurationPageActionGroup" stepKey="goToGeneralConfigReset1"/> <actionGroup ref="AdminSwitchWebsiteActionGroup" stepKey="AdminSwitchStoreViewActionGroup1"> <argument name="website" value="_defaultWebsite"/> </actionGroup> diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml index e7ba97ad36785..ce04b377300f8 100644 --- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml +++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyCategoryProductAndProductCategoryPartialReindexTest.xml @@ -58,9 +58,9 @@ <argument name="categoryName" value="$$categoryN.name$$, $$categoryM.name$$"/> </actionGroup> - <wait stepKey="waitBeforeRunCronIndex" time="30"/> + <wait stepKey="waitBeforeRunCronIndex" time="60"/> <magentoCLI stepKey="runCronIndex" command="cron:run --group=index"/> - <wait stepKey="waitAfterRunCronIndex" time="60"/> + <wait stepKey="waitAfterRunCronIndex" time="120"/> </before> <after> <!-- Change "Category Products" and "Product Categories" indexers to "Update on Save" mode --> @@ -108,6 +108,8 @@ <argument name="categoryName" value="$$categoryK.name$$"/> </actionGroup> + <wait stepKey="waitAfterAssignCategoryK" time="60"/> + <!-- Unassign category M from Product B --> <actionGroup ref="AdminProductPageOpenByIdActionGroup" stepKey="amOnEditCategoryPageB"> <argument name="productId" value="$$productB.id$$"/> @@ -147,8 +149,9 @@ <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="seeProductInCategoryN"/> <!-- Run cron --> - <wait stepKey="waitBeforeRunMagentoCron" time="30"/> + <wait stepKey="waitBeforeRunMagentoCron" time="60"/> <magentoCLI stepKey="runMagentoCron" command="cron:run --group=index"/> + <wait stepKey="waitAfterRunMagentoCron" time="90"/> <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> @@ -213,8 +216,9 @@ <see userInput="$$productC.name$$" selector="{{StorefrontCategoryMainSection.productName}}" stepKey="productCOnCategoryN"/> <!-- Run Cron once to reindex product changes --> - <wait stepKey="waitBeforeRunCronIndexAfterProductAssignToCategory" time="30"/> + <wait stepKey="waitBeforeRunCronIndexAfterProductAssignToCategory" time="60"/> <magentoCLI stepKey="runCronIndexAfterProductAssignToCategory" command="cron:run --group=index"/> + <wait stepKey="waitAfterRunCronIndexAfterProductAssignToCategory" time="90"/> <!-- Open categories K, L, M, N on Storefront in order to make sure that new assigments are applied --> diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Flat/Plugin/StoreGroupTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Flat/Plugin/StoreGroupTest.php index 7dba4ddfd3d8c..cb4fd12e8d6f2 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Flat/Plugin/StoreGroupTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Flat/Plugin/StoreGroupTest.php @@ -11,7 +11,6 @@ use Magento\Catalog\Model\Indexer\Category\Flat\State; use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\Group as GroupModel; use Magento\Store\Model\ResourceModel\Group; use PHPUnit\Framework\MockObject\MockObject; @@ -22,32 +21,32 @@ class StoreGroupTest extends TestCase /** * @var MockObject|IndexerInterface */ - protected $indexerMock; + private $indexerMock; /** * @var MockObject|State */ - protected $stateMock; + private $stateMock; /** * @var StoreGroup */ - protected $model; + private $model; /** * @var MockObject|Group */ - protected $subjectMock; + private $subjectMock; /** * @var IndexerRegistry|MockObject */ - protected $indexerRegistryMock; + private $indexerRegistryMock; /** * @var MockObject|GroupModel */ - protected $groupMock; + private $groupMock; protected function setUp(): void { @@ -70,14 +69,10 @@ protected function setUp(): void $this->indexerRegistryMock = $this->createPartialMock(IndexerRegistry::class, ['get']); - $this->model = (new ObjectManager($this)) - ->getObject( - StoreGroup::class, - ['indexerRegistry' => $this->indexerRegistryMock, 'state' => $this->stateMock] - ); + $this->model = new StoreGroup($this->indexerRegistryMock, $this->stateMock); } - public function testBeforeAndAfterSave() + public function testAfterSave(): void { $this->stateMock->expects($this->once())->method('isFlatEnabled')->willReturn(true); $this->indexerMock->expects($this->once())->method('invalidate'); @@ -90,14 +85,14 @@ public function testBeforeAndAfterSave() ->with('root_category_id') ->willReturn(true); $this->groupMock->expects($this->once())->method('isObjectNew')->willReturn(false); - $this->model->beforeSave($this->subjectMock, $this->groupMock); + $this->assertSame( $this->subjectMock, $this->model->afterSave($this->subjectMock, $this->subjectMock, $this->groupMock) ); } - public function testBeforeAndAfterSaveNotNew() + public function testAfterSaveNotNew(): void { $this->stateMock->expects($this->never())->method('isFlatEnabled'); $this->groupMock->expects($this->once()) @@ -105,7 +100,7 @@ public function testBeforeAndAfterSaveNotNew() ->with('root_category_id') ->willReturn(true); $this->groupMock->expects($this->once())->method('isObjectNew')->willReturn(true); - $this->model->beforeSave($this->subjectMock, $this->groupMock); + $this->assertSame( $this->subjectMock, $this->model->afterSave($this->subjectMock, $this->subjectMock, $this->groupMock) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Flat/Plugin/StoreViewTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Flat/Plugin/StoreViewTest.php index 6f39cc9a7b220..d818af8f1233c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Flat/Plugin/StoreViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Flat/Plugin/StoreViewTest.php @@ -12,6 +12,7 @@ use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; use Magento\Store\Model\ResourceModel\Store; +use Magento\Store\Model\Store as StoreModel; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -20,27 +21,27 @@ class StoreViewTest extends TestCase /** * @var MockObject|IndexerInterface */ - protected $indexerMock; + private $indexerMock; /** * @var MockObject|State */ - protected $stateMock; + private $stateMock; /** * @var StoreView */ - protected $model; + private $model; /** * @var IndexerRegistry|MockObject */ - protected $indexerRegistryMock; + private $indexerRegistryMock; /** - * @var MockObject + * @var Store|MockObject */ - protected $subjectMock; + private $subjectMock; protected function setUp(): void { @@ -65,50 +66,51 @@ protected function setUp(): void $this->model = new StoreView($this->indexerRegistryMock, $this->stateMock); } - public function testBeforeAndAfterSaveNewObject() + public function testAfterSaveNewObject(): void { $this->mockConfigFlatEnabled(); $this->mockIndexerMethods(); $storeMock = $this->createPartialMock( - \Magento\Store\Model\Store::class, + StoreModel::class, ['isObjectNew', 'dataHasChangedFor'] ); $storeMock->expects($this->once())->method('isObjectNew')->willReturn(true); - $this->model->beforeSave($this->subjectMock, $storeMock); + $this->assertSame( $this->subjectMock, $this->model->afterSave($this->subjectMock, $this->subjectMock, $storeMock) ); } - public function testBeforeAndAfterSaveHasChanged() + public function testAfterSaveHasChanged(): void { $storeMock = $this->createPartialMock( - \Magento\Store\Model\Store::class, + StoreModel::class, ['isObjectNew', 'dataHasChangedFor'] ); - $this->model->beforeSave($this->subjectMock, $storeMock); + $this->assertSame( $this->subjectMock, $this->model->afterSave($this->subjectMock, $this->subjectMock, $storeMock) ); } - public function testBeforeAndAfterSaveNoNeed() + public function testAfterSaveNoNeed(): void { $this->mockConfigFlatEnabledNever(); + $storeMock = $this->createPartialMock( - \Magento\Store\Model\Store::class, + StoreModel::class, ['isObjectNew', 'dataHasChangedFor'] ); - $this->model->beforeSave($this->subjectMock, $storeMock); + $this->assertSame( $this->subjectMock, $this->model->afterSave($this->subjectMock, $this->subjectMock, $storeMock) ); } - protected function mockIndexerMethods() + private function mockIndexerMethods(): void { $this->indexerMock->expects($this->once())->method('invalidate'); $this->indexerRegistryMock->expects($this->once()) @@ -117,12 +119,12 @@ protected function mockIndexerMethods() ->willReturn($this->indexerMock); } - protected function mockConfigFlatEnabled() + private function mockConfigFlatEnabled(): void { $this->stateMock->expects($this->once())->method('isFlatEnabled')->willReturn(true); } - protected function mockConfigFlatEnabledNever() + private function mockConfigFlatEnabledNever(): void { $this->stateMock->expects($this->never())->method('isFlatEnabled'); } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreGroupTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreGroupTest.php index 500f7f714e159..f7064b7e1117c 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreGroupTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreGroupTest.php @@ -9,9 +9,9 @@ use Magento\Catalog\Model\Indexer\Category\Product; use Magento\Catalog\Model\Indexer\Category\Product\Plugin\StoreGroup; +use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer; use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Store\Model\Group as GroupModel; use Magento\Store\Model\ResourceModel\Group; use PHPUnit\Framework\MockObject\MockObject; @@ -22,7 +22,7 @@ class StoreGroupTest extends TestCase /** * @var GroupModel|MockObject */ - private $groupMock; + private $groupModelMock; /** * @var MockObject|IndexerInterface @@ -32,13 +32,18 @@ class StoreGroupTest extends TestCase /** * @var MockObject|Group */ - private $subject; + private $subjectMock; /** * @var IndexerRegistry|MockObject */ private $indexerRegistryMock; + /** + * @var TableMaintainer|MockObject + */ + private $tableMaintainerMock; + /** * @var StoreGroup */ @@ -46,7 +51,7 @@ class StoreGroupTest extends TestCase protected function setUp(): void { - $this->groupMock = $this->createPartialMock( + $this->groupModelMock = $this->createPartialMock( GroupModel::class, ['dataHasChangedFor', 'isObjectNew'] ); @@ -59,44 +64,48 @@ protected function setUp(): void true, ['getId', 'getState'] ); - $this->subject = $this->createMock(Group::class); + $this->subjectMock = $this->createMock(Group::class); $this->indexerRegistryMock = $this->createPartialMock(IndexerRegistry::class, ['get']); + $this->tableMaintainerMock = $this->createMock(TableMaintainer::class); - $this->model = (new ObjectManager($this)) - ->getObject(StoreGroup::class, ['indexerRegistry' => $this->indexerRegistryMock]); + $this->model = new StoreGroup($this->indexerRegistryMock, $this->tableMaintainerMock); } /** * @param array $valueMap * @dataProvider changedDataProvider */ - public function testBeforeAndAfterSave($valueMap) + public function testAfterSave(array $valueMap): void { $this->mockIndexerMethods(); - $this->groupMock->expects($this->exactly(2))->method('dataHasChangedFor')->willReturnMap($valueMap); - $this->groupMock->expects($this->once())->method('isObjectNew')->willReturn(false); + $this->groupModelMock->expects($this->exactly(2))->method('dataHasChangedFor')->willReturnMap($valueMap); + $this->groupModelMock->expects($this->once())->method('isObjectNew')->willReturn(false); - $this->model->beforeSave($this->subject, $this->groupMock); - $this->assertSame($this->subject, $this->model->afterSave($this->subject, $this->subject, $this->groupMock)); + $this->assertSame( + $this->subjectMock, + $this->model->afterSave($this->subjectMock, $this->subjectMock, $this->groupModelMock) + ); } /** * @param array $valueMap * @dataProvider changedDataProvider */ - public function testBeforeAndAfterSaveNotNew($valueMap) + public function testAfterSaveNotNew(array $valueMap): void { - $this->groupMock->expects($this->exactly(2))->method('dataHasChangedFor')->willReturnMap($valueMap); - $this->groupMock->expects($this->once())->method('isObjectNew')->willReturn(true); + $this->groupModelMock->expects($this->exactly(2))->method('dataHasChangedFor')->willReturnMap($valueMap); + $this->groupModelMock->expects($this->once())->method('isObjectNew')->willReturn(true); - $this->model->beforeSave($this->subject, $this->groupMock); - $this->assertSame($this->subject, $this->model->afterSave($this->subject, $this->subject, $this->groupMock)); + $this->assertSame( + $this->subjectMock, + $this->model->afterSave($this->subjectMock, $this->subjectMock, $this->groupModelMock) + ); } /** * @return array */ - public function changedDataProvider() + public function changedDataProvider(): array { return [ [ @@ -106,18 +115,20 @@ public function changedDataProvider() ]; } - public function testBeforeAndAfterSaveWithoutChanges() + public function testAfterSaveWithoutChanges(): void { - $this->groupMock->expects($this->exactly(2)) + $this->groupModelMock->expects($this->exactly(2)) ->method('dataHasChangedFor') ->willReturnMap([['root_category_id', false], ['website_id', false]]); - $this->groupMock->expects($this->never())->method('isObjectNew'); + $this->groupModelMock->expects($this->never())->method('isObjectNew'); - $this->model->beforeSave($this->subject, $this->groupMock); - $this->assertSame($this->subject, $this->model->afterSave($this->subject, $this->subject, $this->groupMock)); + $this->assertSame( + $this->subjectMock, + $this->model->afterSave($this->subjectMock, $this->subjectMock, $this->groupModelMock) + ); } - private function mockIndexerMethods() + private function mockIndexerMethods(): void { $this->indexerMock->expects($this->once())->method('invalidate'); $this->indexerRegistryMock->expects($this->once()) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php index 3108934d50f5a..4ecc2404153ba 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Category/Product/Plugin/StoreViewTest.php @@ -27,27 +27,27 @@ class StoreViewTest extends TestCase /** * @var MockObject|IndexerInterface */ - protected $indexerMock; + private $indexerMock; /** * @var StoreView */ - protected $model; + private $model; /** * @var IndexerRegistry|MockObject */ - protected $indexerRegistryMock; + private $indexerRegistryMock; /** * @var TableMaintainer|MockObject */ - protected $tableMaintainer; + private $tableMaintainerMock; /** - * @var MockObject + * @var Group|MockObject */ - protected $subject; + private $subjectMock; protected function setUp(): void { @@ -60,7 +60,7 @@ protected function setUp(): void true, ['getId', 'getState'] ); - $this->subject = $this->createMock(Group::class); + $this->subjectMock = $this->createMock(Group::class); $this->indexerRegistryMock = $this->createPartialMock(IndexerRegistry::class, ['get']); $this->storeMock = $this->createPartialMock( Store::class, @@ -70,47 +70,56 @@ protected function setUp(): void 'dataHasChangedFor' ] ); - $this->tableMaintainer = $this->createPartialMock( + $this->tableMaintainerMock = $this->createPartialMock( TableMaintainer::class, [ 'createTablesForStore' ] ); - $this->model = new StoreView($this->indexerRegistryMock, $this->tableMaintainer); + $this->model = new StoreView($this->indexerRegistryMock, $this->tableMaintainerMock); } - public function testAroundSaveNewObject() + public function testAfterSaveNewObject(): void { $this->mockIndexerMethods(); $this->storeMock->expects($this->atLeastOnce())->method('isObjectNew')->willReturn(true); $this->storeMock->expects($this->atLeastOnce())->method('getId')->willReturn(1); - $this->model->beforeSave($this->subject, $this->storeMock); - $this->assertSame($this->subject, $this->model->afterSave($this->subject, $this->subject, $this->storeMock)); + + $this->assertSame( + $this->subjectMock, + $this->model->afterSave($this->subjectMock, $this->subjectMock, $this->storeMock) + ); } - public function testAroundSaveHasChanged() + public function testAfterSaveHasChanged(): void { $this->mockIndexerMethods(); $this->storeMock->expects($this->once()) ->method('dataHasChangedFor') ->with('group_id') ->willReturn(true); - $this->model->beforeSave($this->subject, $this->storeMock); - $this->assertSame($this->subject, $this->model->afterSave($this->subject, $this->subject, $this->storeMock)); + + $this->assertSame( + $this->subjectMock, + $this->model->afterSave($this->subjectMock, $this->subjectMock, $this->storeMock) + ); } - public function testAroundSaveNoNeed() + public function testAfterSaveNoNeed(): void { $this->storeMock->expects($this->once()) ->method('dataHasChangedFor') ->with('group_id') ->willReturn(false); - $this->model->beforeSave($this->subject, $this->storeMock); - $this->assertSame($this->subject, $this->model->afterSave($this->subject, $this->subject, $this->storeMock)); + + $this->assertSame( + $this->subjectMock, + $this->model->afterSave($this->subjectMock, $this->subjectMock, $this->storeMock) + ); } - private function mockIndexerMethods() + private function mockIndexerMethods(): void { $this->indexerMock->expects($this->once())->method('invalidate'); $this->indexerRegistryMock->expects($this->once()) diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Plugin/StoreViewTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Plugin/StoreViewTest.php index 0d5a3bb93a6bd..4b2c803c66744 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Plugin/StoreViewTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Plugin/StoreViewTest.php @@ -11,50 +11,81 @@ use Magento\Catalog\Model\Indexer\Product\Eav\Processor; use Magento\Framework\Model\AbstractModel; use Magento\Store\Model\ResourceModel\Store; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; class StoreViewTest extends TestCase { /** - * @param array $data - * @dataProvider beforeSaveDataProvider + * @var Processor|MockObject + */ + private $eavProcessorMock; + /** + * @var Store|MockObject */ - public function testBeforeSave(array $data) + private $subjectMock; + /** + * @var AbstractModel|MockObject + */ + private $objectMock; + + /** + * @var StoreView + */ + private $storeViewPlugin; + + protected function setUp(): void { - $eavProcessorMock = $this->getMockBuilder(Processor::class) + $this->eavProcessorMock = $this->getMockBuilder(Processor::class) ->disableOriginalConstructor() ->getMock(); - $matcher = $data['matcher']; - $eavProcessorMock->expects($this->$matcher()) - ->method('markIndexerAsInvalid'); - $subjectMock = $this->getMockBuilder(Store::class) + $this->subjectMock = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->getMock(); - $objectMock = $this->getMockBuilder(AbstractModel::class) + $this->objectMock = $this->getMockBuilder(AbstractModel::class) ->disableOriginalConstructor() ->setMethods(['getId', 'dataHasChangedFor', 'getIsActive']) ->getMock(); - $objectMock->expects($this->any()) + + $this->storeViewPlugin = new StoreView($this->eavProcessorMock); + } + + /** + * @param array $data + * @dataProvider beforeSaveDataProvider + */ + public function testAfterSave(array $data): void + { + $matcher = $data['matcher']; + + $this->eavProcessorMock->expects($this->$matcher()) + ->method('markIndexerAsInvalid'); + + $this->objectMock->expects($this->any()) ->method('getId') ->willReturn($data['object_id']); - $objectMock->expects($this->any()) + + $this->objectMock->expects($this->any()) ->method('dataHasChangedFor') ->with('group_id') ->willReturn($data['has_group_id_changed']); - $objectMock->expects($this->any()) + + $this->objectMock->expects($this->any()) ->method('getIsActive') ->willReturn($data['is_active']); - $model = new StoreView($eavProcessorMock); - $model->beforeSave($subjectMock, $objectMock); + $this->assertSame( + $this->subjectMock, + $this->storeViewPlugin->afterSave($this->subjectMock, $this->subjectMock, $this->objectMock) + ); } /** * @return array */ - public function beforeSaveDataProvider() + public function beforeSaveDataProvider(): array { return [ [ diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/StoreGroupTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/StoreGroupTest.php index 5500081d5e11d..adabfab1560dd 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/StoreGroupTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/StoreGroupTest.php @@ -9,6 +9,7 @@ use Magento\Catalog\Model\Indexer\Product\Flat\Plugin\StoreGroup; use Magento\Catalog\Model\Indexer\Product\Flat\Processor; +use Magento\Store\Model\Group as StoreGroupModel; use Magento\Store\Model\ResourceModel\Group; use Magento\Store\Model\Store; use PHPUnit\Framework\MockObject\MockObject; @@ -19,17 +20,22 @@ class StoreGroupTest extends TestCase /** * @var Processor|MockObject */ - protected $processorMock; + private $processorMock; /** * @var Store|MockObject */ - protected $storeGroupMock; + private $storeGroupMock; /** * @var MockObject */ - protected $subjectMock; + private $subjectMock; + + /** + * @var StoreGroup + */ + private $storeGroupPlugin; protected function setUp(): void { @@ -40,9 +46,11 @@ protected function setUp(): void $this->subjectMock = $this->createMock(Group::class); $this->storeGroupMock = $this->createPartialMock( - \Magento\Store\Model\Group::class, + StoreGroupModel::class, ['getId', 'dataHasChangedFor'] ); + + $this->storeGroupPlugin = new StoreGroup($this->processorMock); } /** @@ -50,14 +58,16 @@ protected function setUp(): void * @param int|null $storeId * @dataProvider storeGroupDataProvider */ - public function testBeforeSave($matcherMethod, $storeId) + public function testAfterSave(string $matcherMethod, ?int $storeId): void { $this->processorMock->expects($this->{$matcherMethod}())->method('markIndexerAsInvalid'); $this->storeGroupMock->expects($this->once())->method('getId')->willReturn($storeId); - $model = new StoreGroup($this->processorMock); - $model->beforeSave($this->subjectMock, $this->storeGroupMock); + $this->assertSame( + $this->subjectMock, + $this->storeGroupPlugin->afterSave($this->subjectMock, $this->subjectMock, $this->storeGroupMock) + ); } /** @@ -65,30 +75,25 @@ public function testBeforeSave($matcherMethod, $storeId) * @param bool $websiteChanged * @dataProvider storeGroupWebsiteDataProvider */ - public function testChangedWebsiteBeforeSave($matcherMethod, $websiteChanged) + public function testAfterSaveChangedWebsite(string $matcherMethod, bool $websiteChanged): void { $this->processorMock->expects($this->{$matcherMethod}())->method('markIndexerAsInvalid'); $this->storeGroupMock->expects($this->once())->method('getId')->willReturn(1); - $this->storeGroupMock->expects( - $this->once() - )->method( - 'dataHasChangedFor' - )->with( - 'root_category_id' - )->willReturn( - $websiteChanged - ); + $this->storeGroupMock->expects($this->once())->method('dataHasChangedFor') + ->with('root_category_id')->willReturn($websiteChanged); - $model = new StoreGroup($this->processorMock); - $model->beforeSave($this->subjectMock, $this->storeGroupMock); + $this->assertSame( + $this->subjectMock, + $this->storeGroupPlugin->afterSave($this->subjectMock, $this->subjectMock, $this->storeGroupMock) + ); } /** * @return array */ - public function storeGroupWebsiteDataProvider() + public function storeGroupWebsiteDataProvider(): array { return [['once', true], ['never', false]]; } @@ -96,7 +101,7 @@ public function storeGroupWebsiteDataProvider() /** * @return array */ - public function storeGroupDataProvider() + public function storeGroupDataProvider(): array { return [['once', null], ['never', 1]]; } diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/StoreTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/StoreTest.php index 1b74039fa944c..219149a57d361 100644 --- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/StoreTest.php +++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Flat/Plugin/StoreTest.php @@ -7,7 +7,9 @@ namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Flat\Plugin; +use Magento\Catalog\Model\Indexer\Product\Flat\Plugin\Store as StorePlugin; use Magento\Catalog\Model\Indexer\Product\Flat\Processor; +use Magento\Store\Model\ResourceModel\Store as StoreResourceModel; use Magento\Store\Model\Store; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -17,17 +19,22 @@ class StoreTest extends TestCase /** * @var Processor|MockObject */ - protected $processorMock; + private $processorMock; /** * @var Store|MockObject */ - protected $storeMock; + private $storeMock; /** * @var MockObject */ - protected $subjectMock; + private $subjectMock; + + /** + * @var StorePlugin + */ + private $storePlugin; protected function setUp(): void { @@ -36,11 +43,13 @@ protected function setUp(): void ['markIndexerAsInvalid'] ); - $this->subjectMock = $this->createMock(\Magento\Store\Model\ResourceModel\Store::class); + $this->subjectMock = $this->createMock(StoreResourceModel::class); $this->storeMock = $this->createPartialMock( Store::class, ['getId', 'dataHasChangedFor'] ); + + $this->storePlugin = new StorePlugin($this->processorMock); } /** @@ -48,14 +57,16 @@ protected function setUp(): void * @param int|null $storeId * @dataProvider storeDataProvider */ - public function testBeforeSave($matcherMethod, $storeId) + public function testAfterSave(string $matcherMethod, ?int $storeId): void { $this->processorMock->expects($this->{$matcherMethod}())->method('markIndexerAsInvalid'); $this->storeMock->expects($this->once())->method('getId')->willReturn($storeId); - $model = new \Magento\Catalog\Model\Indexer\Product\Flat\Plugin\Store($this->processorMock); - $model->beforeSave($this->subjectMock, $this->storeMock); + $this->assertSame( + $this->subjectMock, + $this->storePlugin->afterSave($this->subjectMock, $this->subjectMock, $this->storeMock) + ); } /** @@ -63,30 +74,25 @@ public function testBeforeSave($matcherMethod, $storeId) * @param bool $storeGroupChanged * @dataProvider storeGroupDataProvider */ - public function testBeforeSaveSwitchStoreGroup($matcherMethod, $storeGroupChanged) + public function testAfterSaveSwitchStoreGroup(string $matcherMethod, bool $storeGroupChanged): void { $this->processorMock->expects($this->{$matcherMethod}())->method('markIndexerAsInvalid'); $this->storeMock->expects($this->once())->method('getId')->willReturn(1); - $this->storeMock->expects( - $this->once() - )->method( - 'dataHasChangedFor' - )->with( - 'group_id' - )->willReturn( - $storeGroupChanged - ); + $this->storeMock->expects($this->once())->method('dataHasChangedFor') + ->with('group_id')->willReturn($storeGroupChanged); - $model = new \Magento\Catalog\Model\Indexer\Product\Flat\Plugin\Store($this->processorMock); - $model->beforeSave($this->subjectMock, $this->storeMock); + $this->assertSame( + $this->subjectMock, + $this->storePlugin->afterSave($this->subjectMock, $this->subjectMock, $this->storeMock) + ); } /** * @return array */ - public function storeGroupDataProvider() + public function storeGroupDataProvider(): array { return [['once', true], ['never', false]]; } @@ -94,7 +100,7 @@ public function storeGroupDataProvider() /** * @return array */ - public function storeDataProvider() + public function storeDataProvider(): array { return [['once', null], ['never', 1]]; } diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml index a0aa48fb76b13..ddd66a5bf04bd 100644 --- a/app/code/Magento/Catalog/etc/db_schema.xml +++ b/app/code/Magento/Catalog/etc/db_schema.xml @@ -154,7 +154,7 @@ default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/> - <column xsi:type="text" name="value" nullable="true" comment="Value"/> + <column xsi:type="mediumtext" name="value" nullable="true" comment="Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> </constraint> @@ -408,7 +408,7 @@ default="0" comment="Store ID"/> <column xsi:type="int" name="entity_id" unsigned="true" nullable="false" identity="false" default="0" comment="Entity ID"/> - <column xsi:type="text" name="value" nullable="true" comment="Value"/> + <column xsi:type="mediumtext" name="value" nullable="true" comment="Value"/> <constraint xsi:type="primary" referenceId="PRIMARY"> <column name="value_id"/> </constraint> diff --git a/app/code/Magento/Catalog/view/adminhtml/web/js/components/product-ui-select.js b/app/code/Magento/Catalog/view/adminhtml/web/js/components/product-ui-select.js index fb7ea7a5bcd69..c04daf07db3dd 100644 --- a/app/code/Magento/Catalog/view/adminhtml/web/js/components/product-ui-select.js +++ b/app/code/Magento/Catalog/view/adminhtml/web/js/components/product-ui-select.js @@ -3,6 +3,9 @@ * See COPYING.txt for license details. */ +/** + * @deprecated see Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js + */ define([ 'Magento_Ui/js/form/element/ui-select', 'jquery', diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml index c4f9bc26ee9f3..9ed36098ab6eb 100644 --- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml @@ -180,4 +180,12 @@ <argument name="templateFilterModel" xsi:type="string">Magento\Widget\Model\Template\FilterEmulate</argument> </arguments> </type> + <type name="Magento\WishlistGraphQl\Model\Resolver\Type\WishlistItemType"> + <arguments> + <argument name="supportedTypes" xsi:type="array"> + <item name="simple" xsi:type="string">SimpleWishlistItem</item> + <item name="virtual" xsi:type="string">VirtualWishlistItem</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls index 35f2c767b3e1e..35067a6cb99af 100644 --- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls +++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls @@ -492,3 +492,9 @@ type StoreConfig @doc(description: "The type contains information about a store catalog_default_sort_by : String @doc(description: "Default Sort By.") root_category_id: Int @doc(description: "The ID of the root category") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\RootCategoryId") } + +type SimpleWishlistItem implements WishlistItemInterface @doc(description: "A simple product wish list Item") { +} + +type VirtualWishlistItem implements WishlistItemInterface @doc(description: "A virtual product wish list item") { +} diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php index 35231b8460b19..2ad7ca9f14963 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/ProductPriceIndexFilter.php @@ -105,7 +105,7 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = } if (!empty($entityIds)) { - $select->where('stock_item.product_id in (?)', $entityIds); + $select->where('stock_item.product_id in (?)', $entityIds, \Zend_Db::INT_TYPE); } $select->group('stock_item.product_id'); diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php index b3fa07479a712..f1cef90fc68ca 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php @@ -118,7 +118,7 @@ private function getProductStockStatuses(array $productIds) 'cpr.parent_id = cpe.' . $linkField, ['parent_id' => 'cpe.entity_id'] ) - ->where('product_id IN (?)', $productIds) + ->where('product_id IN (?)', $productIds, \Zend_Db::INT_TYPE) ->where('stock_id = ?', Stock::DEFAULT_STOCK_ID) ->where('website_id = ?', $this->stockConfiguration->getDefaultScopeId()); diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Plugin/StoreGroup.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Plugin/StoreGroup.php index c171e9bda2612..29a8b1e5404fa 100644 --- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Plugin/StoreGroup.php +++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Plugin/StoreGroup.php @@ -3,19 +3,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + namespace Magento\CatalogInventory\Model\Indexer\Stock\Plugin; +use Magento\CatalogInventory\Model\Indexer\Stock\Processor; +use Magento\Framework\Model\AbstractModel; +use Magento\Store\Model\ResourceModel\Group; + class StoreGroup { /** - * @var \Magento\CatalogInventory\Model\Indexer\Stock\Processor + * @var Processor */ protected $_indexerProcessor; /** - * @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $indexerProcessor + * @param Processor $indexerProcessor */ - public function __construct(\Magento\CatalogInventory\Model\Indexer\Stock\Processor $indexerProcessor) + public function __construct(Processor $indexerProcessor) { $this->_indexerProcessor = $indexerProcessor; } @@ -23,18 +28,19 @@ public function __construct(\Magento\CatalogInventory\Model\Indexer\Stock\Proces /** * Before save handler * - * @param \Magento\Store\Model\ResourceModel\Group $subject - * @param \Magento\Framework\Model\AbstractModel $object + * @param Group $subject + * @param Group $result + * @param AbstractModel $object * - * @return void + * @return Group * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeSave( - \Magento\Store\Model\ResourceModel\Group $subject, - \Magento\Framework\Model\AbstractModel $object - ) { - if (!$object->getId() || $object->dataHasChangedFor('website_id')) { + public function afterSave(Group $subject, Group $result, AbstractModel $object) + { + if ($object->isObjectNew() || $object->dataHasChangedFor('website_id')) { $this->_indexerProcessor->markIndexerAsInvalid(); } + + return $result; } } diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php index 1f6f3a16ac617..f994bb8fe26a1 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock.php @@ -138,18 +138,18 @@ public function lockProductsStock(array $productIds, $websiteId) $itemIds = []; $preSelect = $this->getConnection()->select()->from($itemTable, 'item_id') ->where('website_id = ?', $websiteId) - ->where('product_id IN(?)', $productIds); + ->where('product_id IN(?)', $productIds, \Zend_Db::INT_TYPE); foreach ($this->getConnection()->query($preSelect)->fetchAll() as $item) { $itemIds[] = (int)$item['item_id']; } $select = $this->getConnection()->select()->from(['si' => $itemTable]) - ->where('item_id IN (?)', $itemIds) + ->where('item_id IN (?)', $itemIds, \Zend_Db::INT_TYPE) ->forUpdate(true); $productTable = $this->getTable('catalog_product_entity'); $selectProducts = $this->getConnection()->select()->from(['p' => $productTable], []) - ->where('entity_id IN (?)', $productIds) + ->where('entity_id IN (?)', $productIds, \Zend_Db::INT_TYPE) ->columns( [ 'product_id' => 'entity_id', diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php index 25bc0a0ce899e..02e443d09b228 100644 --- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php +++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php @@ -5,9 +5,18 @@ */ namespace Magento\CatalogInventory\Model\ResourceModel\Stock; +use Magento\Catalog\Model\Product; use Magento\CatalogInventory\Api\StockConfigurationInterface; use Magento\CatalogInventory\Model\Stock; +use Magento\Eav\Model\Config; use Magento\Framework\App\ObjectManager; +use Magento\Framework\DB\Select; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\ResourceModel\Db\Context; +use Magento\Store\Model\Store; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\Website; +use Magento\Store\Model\WebsiteFactory; /** * CatalogInventory Stock Status per website Resource Model @@ -19,12 +28,12 @@ * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html * @since 100.0.2 */ -class Status extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb +class Status extends AbstractDb { /** * Store model manager * - * @var \Magento\Store\Model\StoreManagerInterface + * @var StoreManagerInterface * @deprecated 100.1.0 */ protected $_storeManager; @@ -32,12 +41,12 @@ class Status extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb /** * Website model factory * - * @var \Magento\Store\Model\WebsiteFactory + * @var WebsiteFactory */ protected $_websiteFactory; /** - * @var \Magento\Eav\Model\Config + * @var Config */ protected $eavConfig; @@ -47,18 +56,18 @@ class Status extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb private $stockConfiguration; /** - * @param \Magento\Framework\Model\ResourceModel\Db\Context $context - * @param \Magento\Store\Model\StoreManagerInterface $storeManager - * @param \Magento\Store\Model\WebsiteFactory $websiteFactory - * @param \Magento\Eav\Model\Config $eavConfig + * @param Context $context + * @param StoreManagerInterface $storeManager + * @param WebsiteFactory $websiteFactory + * @param Config $eavConfig * @param string $connectionName - * @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration + * @param StockConfigurationInterface $stockConfiguration */ public function __construct( - \Magento\Framework\Model\ResourceModel\Db\Context $context, - \Magento\Store\Model\StoreManagerInterface $storeManager, - \Magento\Store\Model\WebsiteFactory $websiteFactory, - \Magento\Eav\Model\Config $eavConfig, + Context $context, + StoreManagerInterface $storeManager, + WebsiteFactory $websiteFactory, + Config $eavConfig, $connectionName = null, $stockConfiguration = null ) { @@ -128,6 +137,7 @@ public function saveProductStatus( /** * Retrieve product status + * * Return array as key product id, value - stock status * * @param int[] $productIds @@ -151,13 +161,14 @@ public function getProductsStockStatuses($productIds, $websiteId, $stockId = Sto /** * Retrieve websites and default stores + * * Return array as key website_id, value store_id * * @return array */ public function getWebsiteStores() { - /** @var \Magento\Store\Model\Website $website */ + /** @var Website $website */ $website = $this->_websiteFactory->create(); return $this->getConnection()->fetchPairs($website->getDefaultStoresSelect(false)); } @@ -186,6 +197,7 @@ public function getProductsType($productIds) /** * Retrieve Product part Collection array + * * Return array as key product id, value product type * * @param int $lastEntityId @@ -207,12 +219,12 @@ public function getProductCollection($lastEntityId = 0, $limit = 1000) /** * Add stock status to prepare index select * - * @param \Magento\Framework\DB\Select $select - * @param \Magento\Store\Model\Website $website + * @param Select $select + * @param Website $website * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return Status */ - public function addStockStatusToSelect(\Magento\Framework\DB\Select $select, \Magento\Store\Model\Website $website) + public function addStockStatusToSelect(Select $select, Website $website) { $websiteId = $this->getWebsiteId($website->getId()); $select->joinLeft( @@ -225,6 +237,8 @@ public function addStockStatusToSelect(\Magento\Framework\DB\Select $select, \Ma } /** + * Add Stock information to Product Collection + * * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection * @param bool $isFilterInStock * @return \Magento\Catalog\Model\ResourceModel\Product\Collection $collection @@ -289,7 +303,9 @@ public function addIsInStockFilterToCollection($collection) } /** - * @param \Magento\Store\Model\Website $websiteId + * Get website with fallback to default + * + * @param Website $websiteId * @return int */ private function getWebsiteId($websiteId = null) @@ -303,6 +319,7 @@ private function getWebsiteId($websiteId = null) /** * Retrieve Product(s) status for store + * * Return array where key is a product_id, value - status * * @param int[] $productIds @@ -315,17 +332,17 @@ public function getProductStatus($productIds, $storeId = null) $productIds = [$productIds]; } - $attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, 'status'); + $attribute = $this->eavConfig->getAttribute(Product::ENTITY, 'status'); $attributeTable = $attribute->getBackend()->getTable(); $linkField = $attribute->getEntity()->getLinkField(); $connection = $this->getConnection(); - if ($storeId === null || $storeId == \Magento\Store\Model\Store::DEFAULT_STORE_ID) { + if ($storeId === null || $storeId == Store::DEFAULT_STORE_ID) { $select = $connection->select()->from($attributeTable, [$linkField, 'value']) - ->where("{$linkField} IN (?)", $productIds) + ->where("{$linkField} IN (?)", $productIds, \Zend_Db::INT_TYPE) ->where('attribute_id = ?', $attribute->getAttributeId()) - ->where('store_id = ?', \Magento\Store\Model\Store::DEFAULT_STORE_ID); + ->where('store_id = ?', Store::DEFAULT_STORE_ID); $rows = $connection->fetchPairs($select); } else { @@ -337,7 +354,7 @@ public function getProductStatus($productIds, $storeId = null) "t1.{$linkField} = t2.{$linkField} AND t1.attribute_id = t2.attribute_id AND t2.store_id = {$storeId}" )->where( 't1.store_id = ?', - \Magento\Store\Model\Store::DEFAULT_STORE_ID + Store::DEFAULT_STORE_ID )->where( 't1.attribute_id = ?', $attribute->getAttributeId() diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Plugin/StoreGroupTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Plugin/StoreGroupTest.php index 9e2b7f29ce0fa..0e2b6b2f329c1 100644 --- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Plugin/StoreGroupTest.php +++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/Plugin/StoreGroupTest.php @@ -12,7 +12,6 @@ use Magento\CatalogInventory\Model\Indexer\Stock\Plugin\StoreGroup; use Magento\CatalogInventory\Model\Indexer\Stock\Processor; -use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Model\AbstractModel; use Magento\Store\Model\ResourceModel\Group; use PHPUnit\Framework\MockObject\MockObject; @@ -23,24 +22,24 @@ class StoreGroupTest extends TestCase /** * @var StoreGroup */ - protected $_model; + private $model; /** - * @var IndexerInterface|MockObject + * @var Processor|MockObject */ - protected $_indexerMock; + private $indexerProcessorMock; protected function setUp(): void { - $this->_indexerMock = $this->createMock(Processor::class); - $this->_model = new StoreGroup($this->_indexerMock); + $this->indexerProcessorMock = $this->createMock(Processor::class); + $this->model = new StoreGroup($this->indexerProcessorMock); } /** * @param array $data - * @dataProvider beforeSaveDataProvider + * @dataProvider afterSaveDataProvider */ - public function testBeforeSave(array $data) + public function testAfterSave(array $data): void { $subjectMock = $this->createMock(Group::class); $objectMock = $this->createPartialMock( @@ -55,16 +54,19 @@ public function testBeforeSave(array $data) ->with('website_id') ->willReturn($data['has_website_id_changed']); - $this->_indexerMock->expects($this->once()) + $this->indexerProcessorMock->expects($this->once()) ->method('markIndexerAsInvalid'); - $this->_model->beforeSave($subjectMock, $objectMock); + $this->assertSame( + $subjectMock, + $this->model->afterSave($subjectMock, $subjectMock, $objectMock) + ); } /** * @return array */ - public function beforeSaveDataProvider() + public function afterSaveDataProvider(): array { return [ [ diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ProductPriceIndexModifier.php b/app/code/Magento/CatalogRule/Model/Indexer/ProductPriceIndexModifier.php index 404fc32e0c0d4..90e50538bcba3 100644 --- a/app/code/Magento/CatalogRule/Model/Indexer/ProductPriceIndexModifier.php +++ b/app/code/Magento/CatalogRule/Model/Indexer/ProductPriceIndexModifier.php @@ -71,7 +71,7 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = [] ); if ($entityIds) { - $select->where('i.entity_id IN (?)', $entityIds); + $select->where('i.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE); } $finalPrice = $priceTable->getFinalPriceField(); diff --git a/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/ConfigurableProductsProvider.php b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/ConfigurableProductsProvider.php index dd020114b03ab..1ef0490092b40 100644 --- a/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/ConfigurableProductsProvider.php +++ b/app/code/Magento/CatalogRuleConfigurable/Plugin/CatalogRule/Model/ConfigurableProductsProvider.php @@ -27,12 +27,14 @@ public function __construct(\Magento\Framework\App\ResourceConnection $resource) } /** + * Return list of ID for product variation + * * @param array $ids * @return array */ public function getIds(array $ids) { - $key = md5(json_encode($ids)); + $key = md5(json_encode($ids)); //phpcs:ignore if (!isset($this->productIds[$key])) { $connection = $this->resource->getConnection(); $this->productIds[$key] = $connection->fetchCol( @@ -40,7 +42,7 @@ public function getIds(array $ids) ->select() ->from(['e' => $this->resource->getTableName('catalog_product_entity')], ['e.entity_id']) ->where('e.type_id = ?', \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE) - ->where('e.entity_id IN (?)', $ids) + ->where('e.entity_id IN (?)', $ids, \Zend_Db::INT_TYPE) ); } return $this->productIds[$key]; diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php index ffa7dfd80df0c..8c4690f044764 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/DataProvider.php @@ -242,7 +242,7 @@ private function getSelectForSearchableProducts( $this->joinAttribute($select, 'status', $storeId, [Status::STATUS_ENABLED]); if ($productIds !== null) { - $select->where('e.entity_id IN (?)', $productIds); + $select->where('e.entity_id IN (?)', $productIds, \Zend_Db::INT_TYPE); } $select->where('e.entity_id > ?', $lastProductId); $select->order('e.entity_id'); @@ -410,7 +410,8 @@ public function getProductAttributes($storeId, array $productIds, array $attribu [$linkField, 'entity_id'] )->where( 'cpe.entity_id IN (?)', - $productIds + $productIds, + \Zend_Db::INT_TYPE ) ); foreach ($attributeTypes as $backendType => $attributeIds) { diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php index fa4d9fee415cf..3ce8a96fb5070 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php @@ -317,7 +317,7 @@ protected function getProductIdsFromParents(array $entityIds) ->select() ->from(['relation' => $this->getTable('catalog_product_relation')], []) ->distinct(true) - ->where('child_id IN (?)', $entityIds) + ->where('child_id IN (?)', $entityIds, \Zend_Db::INT_TYPE) ->join( ['cpe' => $this->getTable('catalog_product_entity')], 'relation.parent_id = cpe.' . $linkField, diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Store/Group.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Store/Group.php index 73a79f7c87239..e7bbf72bdd7e3 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Store/Group.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Store/Group.php @@ -5,47 +5,29 @@ */ namespace Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Store; +use Magento\CatalogSearch\Model\Indexer\Fulltext as FulltextIndexer; use Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\AbstractPlugin as AbstractIndexerPlugin; -use Magento\Store\Model\ResourceModel\Group as StoreGroupResourceModel; use Magento\Framework\Model\AbstractModel; -use Magento\CatalogSearch\Model\Indexer\Fulltext as FulltextIndexer; +use Magento\Store\Model\ResourceModel\Group as StoreGroupResourceModel; /** * Plugin for Magento\Store\Model\ResourceModel\Group */ class Group extends AbstractIndexerPlugin { - /** - * @var bool - */ - private $needInvalidation; - - /** - * Check if indexer requires invalidation after store group save - * - * @param StoreGroupResourceModel $subject - * @param AbstractModel $group - * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeSave(StoreGroupResourceModel $subject, AbstractModel $group) - { - $this->needInvalidation = !$group->isObjectNew() && $group->dataHasChangedFor('website_id'); - } - /** * Invalidate indexer on store group save * * @param StoreGroupResourceModel $subject * @param StoreGroupResourceModel $result + * @param AbstractModel $group * @return StoreGroupResourceModel * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterSave(StoreGroupResourceModel $subject, StoreGroupResourceModel $result) + public function afterSave(StoreGroupResourceModel $subject, StoreGroupResourceModel $result, AbstractModel $group) { - if ($this->needInvalidation) { + if (!$group->isObjectNew() && $group->dataHasChangedFor('website_id')) { $this->indexerRegistry->get(FulltextIndexer::INDEXER_ID)->invalidate(); } diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Store/View.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Store/View.php index 7f0c5fdae6d42..242a4f3f0c36b 100644 --- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Store/View.php +++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Plugin/Store/View.php @@ -5,47 +5,29 @@ */ namespace Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Store; +use Magento\CatalogSearch\Model\Indexer\Fulltext as FulltextIndexer; use Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\AbstractPlugin as AbstractIndexerPlugin; -use Magento\Store\Model\ResourceModel\Store as StoreResourceModel; use Magento\Framework\Model\AbstractModel; -use Magento\CatalogSearch\Model\Indexer\Fulltext as FulltextIndexer; +use Magento\Store\Model\ResourceModel\Store as StoreResourceModel; /** * Plugin for Magento\Store\Model\ResourceModel\Store */ class View extends AbstractIndexerPlugin { - /** - * @var bool - */ - private $needInvalidation; - - /** - * Check if indexer requires invalidation after store view save - * - * @param StoreResourceModel $subject - * @param AbstractModel $store - * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function beforeSave(StoreResourceModel $subject, AbstractModel $store) - { - $this->needInvalidation = $store->isObjectNew(); - } - /** * Invalidate indexer on store view save * * @param StoreResourceModel $subject * @param StoreResourceModel $result + * @param AbstractModel $store * @return StoreResourceModel * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterSave(StoreResourceModel $subject, StoreResourceModel $result) + public function afterSave(StoreResourceModel $subject, StoreResourceModel $result, AbstractModel $store) { - if ($this->needInvalidation) { + if ($store->isObjectNew()) { $this->indexerRegistry->get(FulltextIndexer::INDEXER_ID)->invalidate(); } diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext.php index 3614cd9dbf3a9..ad6d37c296012 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext.php @@ -116,7 +116,8 @@ public function getRelationsByChild($childIds) ['cpe.entity_id'] )->where( 'relation.child_id IN (?)', - $childIds + $childIds, + \Zend_Db::INT_TYPE ); return $connection->fetchCol($select); diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php index e625ccbe51fe3..d37f0f8a5153b 100644 --- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php +++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php @@ -258,7 +258,8 @@ protected function _getSearchEntityIdsSql($query, $searchOnlyInCurrentStore = tr [] )->where( 't1.attribute_id IN (?)', - $attributeIds + $attributeIds, + \Zend_Db::INT_TYPE )->where( 't1.store_id = ?', 0 @@ -332,7 +333,8 @@ protected function _getSearchInOptionSql($query) 'd.store_id=0' )->where( 'o.attribute_id IN (?)', - $attributeIds + $attributeIds, + \Zend_Db::INT_TYPE )->where( $this->_resourceHelper->getCILike($ifValue, $this->_searchQuery, ['position' => 'any']) ); diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminOpenCatalogSearchTermIndexPageActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminOpenCatalogSearchTermIndexPageActionGroup.xml new file mode 100644 index 0000000000000..e8528d4126376 --- /dev/null +++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminOpenCatalogSearchTermIndexPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenCatalogSearchTermIndexPageActionGroup"> + <annotations> + <description>Open catalog search term index page.</description> + </annotations> + + <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openCatalogSearchTermIndexPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml index b42313fc14773..26280ed67d183 100644 --- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml +++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml @@ -39,8 +39,7 @@ <deleteData createDataKey="createCategory1" stepKey="deleteCategory1"/> <!-- Delete all search terms --> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> - <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage"/> <comment userInput="Delete all search terms" stepKey="deleteAllSearchTermsComment"/> <actionGroup ref="AdminDeleteAllSearchTermsActionGroup" stepKey="deleteAllSearchTerms"/> @@ -53,8 +52,7 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin1"/> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage1"/> - <waitForPageLoad stepKey="waitForPageLoad1"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage1"/> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByFirstSearchQuery1"> <argument name="searchQuery" value="$$createProduct1.name$$"/> @@ -67,8 +65,7 @@ <argument name="searchTerm" value="UpdatedSearchTermData1"/> </actionGroup> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage2"/> - <waitForPageLoad stepKey="waitForPageLoad3"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage2"/> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByFirstSearchQuery2"> <argument name="searchQuery" value="{{UpdatedSearchTermData1.query_text}}"/> diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/Store/GroupTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/Store/GroupTest.php index b4a27a4350131..302dd0f1e2684 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/Store/GroupTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/Store/GroupTest.php @@ -11,7 +11,6 @@ use Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Store\Group as StoreGroupIndexerPlugin; use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Store\Model\Group as StoreGroup; use Magento\Store\Model\ResourceModel\Group as StoreGroupResourceModel; use PHPUnit\Framework\MockObject\MockObject; @@ -24,11 +23,6 @@ class GroupTest extends TestCase */ private $plugin; - /** - * @var ObjectManagerHelper - */ - private $objectManagerHelper; - /** * @var IndexerRegistry|MockObject */ @@ -64,11 +58,7 @@ protected function setUp(): void ->setMethods(['dataHasChangedFor', 'isObjectNew']) ->getMock(); - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->plugin = $this->objectManagerHelper->getObject( - StoreGroupIndexerPlugin::class, - ['indexerRegistry' => $this->indexerRegistryMock] - ); + $this->plugin = new StoreGroupIndexerPlugin($this->indexerRegistryMock); } /** @@ -76,9 +66,9 @@ protected function setUp(): void * @param bool $websiteChanged * @param int $invalidateCounter * @return void - * @dataProvider beforeAfterSaveDataProvider + * @dataProvider afterSaveDataProvider */ - public function testBeforeAfterSave($isObjectNew, $websiteChanged, $invalidateCounter) + public function testAfterSave(bool $isObjectNew, bool $websiteChanged, int $invalidateCounter): void { $this->prepareIndexer($invalidateCounter); $this->storeGroupMock->expects(static::any()) @@ -91,14 +81,16 @@ public function testBeforeAfterSave($isObjectNew, $websiteChanged, $invalidateCo $this->indexerMock->expects(static::exactly($invalidateCounter)) ->method('invalidate'); - $this->plugin->beforeSave($this->subjectMock, $this->storeGroupMock); - $this->assertSame($this->subjectMock, $this->plugin->afterSave($this->subjectMock, $this->subjectMock)); + $this->assertSame( + $this->subjectMock, + $this->plugin->afterSave($this->subjectMock, $this->subjectMock, $this->storeGroupMock) + ); } /** * @return array */ - public function beforeAfterSaveDataProvider() + public function afterSaveDataProvider(): array { return [ [false, false, 0], @@ -108,13 +100,16 @@ public function beforeAfterSaveDataProvider() ]; } - public function testAfterDelete() + public function testAfterDelete(): void { $this->prepareIndexer(1); $this->indexerMock->expects(static::once()) ->method('invalidate'); - $this->assertSame($this->subjectMock, $this->plugin->afterDelete($this->subjectMock, $this->subjectMock)); + $this->assertSame( + $this->subjectMock, + $this->plugin->afterDelete($this->subjectMock, $this->subjectMock) + ); } /** @@ -123,7 +118,7 @@ public function testAfterDelete() * @param int $invalidateCounter * @return void */ - private function prepareIndexer($invalidateCounter) + private function prepareIndexer(int $invalidateCounter): void { $this->indexerRegistryMock->expects(static::exactly($invalidateCounter)) ->method('get') diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/Store/ViewTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/Store/ViewTest.php index f778c9340cbd7..23e1c44c5f7c8 100644 --- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/Store/ViewTest.php +++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Indexer/Fulltext/Plugin/Store/ViewTest.php @@ -11,7 +11,6 @@ use Magento\CatalogSearch\Model\Indexer\Fulltext\Plugin\Store\View as StoreViewIndexerPlugin; use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; use Magento\Store\Model\ResourceModel\Store as StoreResourceModel; use Magento\Store\Model\Store; use PHPUnit\Framework\MockObject\MockObject; @@ -24,11 +23,6 @@ class ViewTest extends TestCase */ private $plugin; - /** - * @var ObjectManagerHelper - */ - private $objectManagerHelper; - /** * @var IndexerRegistry|MockObject */ @@ -64,20 +58,16 @@ protected function setUp(): void ->setMethods(['isObjectNew']) ->getMock(); - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->plugin = $this->objectManagerHelper->getObject( - StoreViewIndexerPlugin::class, - ['indexerRegistry' => $this->indexerRegistryMock] - ); + $this->plugin = new StoreViewIndexerPlugin($this->indexerRegistryMock); } /** * @param bool $isObjectNew * @param int $invalidateCounter * - * @dataProvider beforeAfterSaveDataProvider + * @dataProvider afterSaveDataProvider */ - public function testBeforeAfterSave($isObjectNew, $invalidateCounter) + public function testAfterSave(bool $isObjectNew, int $invalidateCounter): void { $this->prepareIndexer($invalidateCounter); $this->storeMock->expects(static::once()) @@ -86,14 +76,16 @@ public function testBeforeAfterSave($isObjectNew, $invalidateCounter) $this->indexerMock->expects(static::exactly($invalidateCounter)) ->method('invalidate'); - $this->plugin->beforeSave($this->subjectMock, $this->storeMock); - $this->assertSame($this->subjectMock, $this->plugin->afterSave($this->subjectMock, $this->subjectMock)); + $this->assertSame( + $this->subjectMock, + $this->plugin->afterSave($this->subjectMock, $this->subjectMock, $this->storeMock) + ); } /** * @return array */ - public function beforeAfterSaveDataProvider() + public function afterSaveDataProvider(): array { return [ [false, 0], @@ -101,7 +93,7 @@ public function beforeAfterSaveDataProvider() ]; } - public function testAfterDelete() + public function testAfterDelete(): void { $this->prepareIndexer(1); $this->indexerMock->expects(static::once()) @@ -116,7 +108,7 @@ public function testAfterDelete() * @param int $invalidateCounter * @return void */ - private function prepareIndexer($invalidateCounter) + private function prepareIndexer(int $invalidateCounter): void { $this->indexerRegistryMock->expects(static::exactly($invalidateCounter)) ->method('get') diff --git a/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php b/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php index 4c84ea5ae764f..16450ec6ff2c2 100644 --- a/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php +++ b/app/code/Magento/Checkout/Block/Checkout/LayoutProcessor.php @@ -351,9 +351,6 @@ private function getBillingAddressComponent($paymentCode, $elements) ], ], 'telephone' => [ - 'validation' => [ - 'validate-phoneStrict' => 0, - ], 'config' => [ 'tooltip' => [ 'description' => __('For delivery questions.'), diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutActionGroup.xml new file mode 100644 index 0000000000000..8210fe1df73ba --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertStorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutActionGroup"> + <annotations> + <description>Checks if visible password field for unregistered email on checkout page</description> + </annotations> + + <waitForPageLoad stepKey="waitForCheckoutPageLoaded"/> + <dontSeeElement selector="{{StorefrontCheckoutCheckoutCustomerLoginSection.password}}" stepKey="checkIfPasswordVisible"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutErrorMessageActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutErrorMessageActionGroup.xml new file mode 100644 index 0000000000000..6db9d9a1f0673 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAssertCheckoutErrorMessageActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontAssertCheckoutErrorMessageActionGroup"> + <arguments> + <argument name="message" type="string"/> + </arguments> + + <waitForElementVisible selector="{{CheckoutCartMessageSection.errorMessageText(message)}}" stepKey="assertErrorMessage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml index cf15cdf15cf15..0c7f200e2b5eb 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartMessageSection.xml @@ -12,5 +12,6 @@ <element name="successMessage" type="text" selector=".message.message-success.success>div" /> <element name="errorMessage" type="text" selector=".message-error.error.message>div" /> <element name="emptyCartMessage" type="text" selector=".cart-empty>p"/> + <element name="errorMessageText" type="text" selector="//div[contains(@class, 'message-error')]/div[text()='{{var}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml index 5a9857f6aaa78..1c9933064154a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutPaymentSection.xml @@ -15,6 +15,8 @@ <element name="billingNewAddressForm" type="text" selector="[data-form='billing-new-address']"/> <element name="billingAddressNotSameCheckbox" type="checkbox" selector="#billing-address-same-as-shipping-checkmo"/> <element name="editAddress" type="button" selector="button.action.action-edit-address"/> + <element name="addressDropdown" type="select" selector="[name=billing_address_id]"/> + <element name="addressDropdownSelected" type="select" selector="[name=billing_address_id] option:checked"/> <element name="placeOrderDisabled" type="button" selector="#checkout-payment-method-load button.disabled"/> <element name="update" type="button" selector=".payment-method._active .payment-method-billing-address .action.action-update"/> <element name="guestFirstName" type="input" selector=".payment-method._active .billing-address-form input[name='firstname']"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldForUKCustomerRemainOptionAfterRefreshTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldForUKCustomerRemainOptionAfterRefreshTest.xml index 7eae5d0d292d1..e7e8f9f0ef699 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldForUKCustomerRemainOptionAfterRefreshTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldForUKCustomerRemainOptionAfterRefreshTest.xml @@ -35,7 +35,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$createProduct.name$$ to your shopping cart." stepKey="seeAddedToCartMessage"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml index 12e1a6e9872d3..7c4b18e1aab89 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/AddressStateFieldShouldNotAcceptJustIntegerValuesTest.xml @@ -38,7 +38,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$createProduct.name$$ to your shopping cart." stepKey="seeAddedToCartMessage"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml index 313f5997e0af0..bf942e70cfa36 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/NoErrorCartCheckoutForProductsDeletedFromMiniCartTest.xml @@ -40,7 +40,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onStorefrontCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addProductToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addProductToCart"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$createSimpleProduct.name$$ to your shopping cart." stepKey="seeAddedToCartMessage"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml index 6a211c3908059..13968964436b4 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/OnePageCheckoutAsCustomerUsingNonDefaultAddressTest.xml @@ -26,7 +26,7 @@ </createData> <!-- Create customer --> - <createData entity="Customer_US_UK_DE" stepKey="createCustomer"/> + <createData entity="Customer_DE_UK_US" stepKey="createCustomer"/> </before> <after> <!-- Admin log out --> @@ -70,7 +70,8 @@ <!-- Change the address --> <click selector="{{CheckoutPaymentSection.editAddress}}" stepKey="editAddress"/> - <waitForElementVisible selector="{{CheckoutShippingSection.addressDropdown}}" stepKey="waitForDropDownToBeVisible"/> + <waitForElementVisible selector="{{CheckoutPaymentSection.addressDropdown}}" stepKey="waitForDropDownToBeVisible"/> + <see selector="{{CheckoutPaymentSection.addressDropdownSelected}}" userInput="{{US_Address_NY.street[0]}}" stepKey="seeDefaultBillingAddressStreet"/> <selectOption selector="{{CheckoutShippingSection.addressDropdown}}" userInput="{{UK_Not_Default_Address.street[0]}}" stepKey="addAddress"/> <!-- Check order summary in checkout --> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRatesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRatesTest.xml index 92150b1e99dd5..9747980801068 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRatesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithMultipleAddressesAndTaxRatesTest.xml @@ -83,7 +83,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$simplecategory.name$$)}}" stepKey="onCategoryPage1"/> <waitForPageLoad stepKey="waitForCatalogPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct1"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart1"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart1"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded1"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$simpleproduct1.name$$ to your shopping cart." stepKey="seeAddedToCartMessage1"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity1"/> @@ -102,7 +102,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$simplecategory.name$$)}}" stepKey="onCategoryPage2"/> <waitForPageLoad stepKey="waitForCatalogPageLoad2"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct2"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart2"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart2"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded2"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$simpleproduct1.name$$ to your shopping cart." stepKey="seeAddedToCartMessage2"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity2"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest.xml index 732038f24834b..d6f1408c2b66a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontCustomerCheckoutTest/StorefrontCustomerCheckoutTestWithRestrictedCountriesForPaymentTest.xml @@ -52,7 +52,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$createProduct.name$$ to your shopping cart." stepKey="seeAddedToCartMessage"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutTest.xml index be5cf143f13dc..b591aefbdc889 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutTest.xml @@ -40,7 +40,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$createProduct.name$$ to your shopping cart." stepKey="seeAddedToCartMessage"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutTestWithRestrictedCountriesForPaymentTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutTestWithRestrictedCountriesForPaymentTest.xml index 7660df18407d5..15b550657ef60 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutTestWithRestrictedCountriesForPaymentTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest/StorefrontGuestCheckoutTestWithRestrictedCountriesForPaymentTest.xml @@ -43,7 +43,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$createProduct.name$$ to your shopping cart." stepKey="seeAddedToCartMessage"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml index 916b4d7bdad84..5a0610f5c5b0a 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutUsingFreeShippingAndTaxesTest.xml @@ -120,8 +120,7 @@ <!-- Create a Tax Rule --> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> - <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAddNewTaxRuleButton"/> <!-- Create a tax rule with defaults --> <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutPhoneValidationTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutPhoneValidationTest.xml deleted file mode 100644 index b001128fee906..0000000000000 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontOnePageCheckoutPhoneValidationTest.xml +++ /dev/null @@ -1,44 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> - -<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> - <test name="StorefrontOnePageCheckoutPhoneValidationTest"> - <annotations> - <features value="Checkout"/> - <stories value="Checkout validation phone field"/> - <title value="Validate phone field on checkout page"/> - <description value="Validate phone field on checkout page, field must not contain alphabetical symbols"/> - <severity value="MAJOR" /> - <testCaseId value="MC-35292"/> - </annotations> - <before> - <createData entity="_defaultCategory" stepKey="createCategory"/> - <createData entity="ApiSimpleProduct" stepKey="createProduct"> - <requiredEntity createDataKey="createCategory"/> - </createData> - </before> - <after> - <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> - <deleteData createDataKey="createProduct" stepKey="deleteProduct"/> - </after> - - <actionGroup ref="StorefrontNavigateCategoryPageActionGroup" stepKey="openCategoryPageOnFrontend"> - <argument name="category" value="$createCategory$"/> - </actionGroup> - - <actionGroup ref="StorefrontAddSimpleProductToCartActionGroup" stepKey="addToCartFromStorefrontProductPage"> - <argument name="product" value="$$createProduct$$"/> - </actionGroup> - - <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="guestGoToCheckout"/> - - <fillField userInput="Sample text" selector="{{CheckoutShippingSection.telephone}}" stepKey="enterAlphabeticalSymbols"/> - <see userInput="Please enter a valid phone number. For example (123) 456-7890 or 123-456-7890." selector="{{CheckoutShippingSection.addressFieldValidationError}}" stepKey="checkPhoneFieldValidationIsPassed"/> - </test> -</tests> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml index b7c1d7b83e9b7..03323b7b9c855 100644 --- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVerifySecureURLRedirectCheckoutTest.xml @@ -28,8 +28,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$category.name$$)}}" stepKey="goToCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="moveMouseOverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="clickAddToCartButton"/> - <waitForPageLoad stepKey="waitForAddToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="clickAddToCartButton"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForAddedToCartSuccessMessage"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$product.name$$ to your shopping cart." stepKey="seeAddedToCartSuccessMessage"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutTest.xml new file mode 100644 index 0000000000000..41b5f734d0096 --- /dev/null +++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutTest.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutTest"> + <annotations> + <features value="Checkout"/> + <stories value="Visible password field for unregistered e-mail on Checkout"/> + <title value="Visibility password field for unregistered e-mail on Checkout process"/> + <description value="Guest should not be able to see password field if entered unregistered email"/> + <severity value="MINOR"/> + <group value="checkout"/> + </annotations> + <before> + <createData entity="SimpleTwo" stepKey="simpleProduct"/> + </before> + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + </after> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="openProductStorefront"> + <argument name="productUrl" value="$$simpleProduct.custom_attributes[url_key]$$"/> + </actionGroup> + <actionGroup ref="StorefrontClickAddToCartOnProductPageActionGroup" stepKey="addToCartFromStorefrontProductPage"/> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="openCheckoutPage"/> + <actionGroup ref="AssertStorefrontEmailTooltipContentOnCheckoutActionGroup" stepKey="assertEmailTooltipContent"/> + <actionGroup ref="AssertStorefrontEmailNoteMessageOnCheckoutActionGroup" stepKey="assertEmailNoteMessage"/> + <actionGroup ref="StorefrontFillEmailFieldOnCheckoutActionGroup" stepKey="fillUnregisteredEmailFirstAttempt"> + <argument name="email" value="unregistered@email.test"/> + </actionGroup> + <actionGroup ref="AssertStorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutActionGroup" stepKey="checkIfPasswordVisibleAfterFieldFilling"/> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="reloadCheckoutPage" /> + <actionGroup ref="AssertStorefrontVisiblePasswordFieldForUnregisteredEmailOnCheckoutActionGroup" + stepKey="checkIfPasswordVisibleAfterPageReload"/> + </test> +</tests> diff --git a/app/code/Magento/Checkout/i18n/en_US.csv b/app/code/Magento/Checkout/i18n/en_US.csv index 4a78f8deae841..85ee4e1f03f0e 100644 --- a/app/code/Magento/Checkout/i18n/en_US.csv +++ b/app/code/Magento/Checkout/i18n/en_US.csv @@ -176,7 +176,7 @@ Summary,Summary "We'll send your order confirmation here.","We'll send your order confirmation here." Payment,Payment "Not yet calculated","Not yet calculated" -"We received your order!","We received your order!" +"The order was not successful!","The order was not successful!" "Thank you for your purchase!","Thank you for your purchase!" "Password", "Password" "Something went wrong while saving the page. Please refresh the page and try again.","Something went wrong while saving the page. Please refresh the page and try again." @@ -184,4 +184,4 @@ Payment,Payment "Items in Cart","Items in Cart" "Close","Close" "Show Cross-sell Items in the Shopping Cart","Show Cross-sell Items in the Shopping Cart" -"You added %1 to your <a href=""%2"">shopping cart</a>.","You added %1 to your <a href=""%2"">shopping cart</a>." \ No newline at end of file +"You added %1 to your <a href=""%2"">shopping cart</a>.","You added %1 to your <a href=""%2"">shopping cart</a>." diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml index ab058110fe66f..192f20653f8c3 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_index_index.xml @@ -223,9 +223,6 @@ </item> </item> <item name="telephone" xsi:type="array"> - <item name="validation" xsi:type="array"> - <item name="validate-phoneStrict" xsi:type="number">0</item> - </item> <item name="config" xsi:type="array"> <item name="tooltip" xsi:type="array"> <item name="description" xsi:type="string" translate="true">For delivery questions.</item> diff --git a/app/code/Magento/Checkout/view/frontend/layout/checkout_onepage_failure.xml b/app/code/Magento/Checkout/view/frontend/layout/checkout_onepage_failure.xml index 3ab37c2ab6b9f..b815bf74c155a 100644 --- a/app/code/Magento/Checkout/view/frontend/layout/checkout_onepage_failure.xml +++ b/app/code/Magento/Checkout/view/frontend/layout/checkout_onepage_failure.xml @@ -9,7 +9,7 @@ <body> <referenceBlock name="page.main.title"> <action method="setPageTitle"> - <argument translate="true" name="title" xsi:type="string">We received your order!</argument> + <argument translate="true" name="title" xsi:type="string">The order was not successful!</argument> </action> </referenceBlock> <referenceContainer name="content"> diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js index ca3a267c01671..80411fb8eb29d 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address/list.js @@ -23,6 +23,9 @@ define([ }, addressOptions = addressList().filter(function (address) { return address.getType() === 'customer-address'; + }), + addressDefaultIndex = addressOptions.findIndex(function (address) { + return address.isDefaultBilling(); }); return Component.extend({ @@ -53,7 +56,8 @@ define([ this._super() .observe('selectedAddress isNewAddressSelected') .observe({ - isNewAddressSelected: !customer.isLoggedIn() || !addressOptions.length + isNewAddressSelected: !customer.isLoggedIn() || !addressOptions.length, + selectedAddress: this.addressOptions[addressDefaultIndex] }); return this; diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js b/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js index 9adfb549a5b1c..8311d97522980 100644 --- a/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js +++ b/app/code/Magento/Checkout/view/frontend/web/js/view/form/element/email.js @@ -113,6 +113,7 @@ define([ $.when(this.isEmailCheckComplete).done(function () { this.isPasswordVisible(false); + checkoutData.setCheckedEmailValue(''); }.bind(this)).fail(function () { this.isPasswordVisible(true); checkoutData.setCheckedEmailValue(this.email()); @@ -192,6 +193,10 @@ define([ * @returns {Boolean} - initial visibility state. */ resolveInitialPasswordVisibility: function () { + if (checkoutData.getInputFieldEmailValue() !== '' && checkoutData.getCheckedEmailValue() !== '') { + return true; + } + if (checkoutData.getInputFieldEmailValue() !== '') { return checkoutData.getInputFieldEmailValue() === checkoutData.getCheckedEmailValue(); } diff --git a/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php b/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php index 9efd24e5003ca..d1c6b0fe7956d 100644 --- a/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php +++ b/app/code/Magento/Cms/Block/Adminhtml/Wysiwyg/Images/Content.php @@ -102,7 +102,7 @@ protected function _construct() 'type' => 'button' ], 0, - 0, + 100, 'header' ); } diff --git a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php index 3244a7d14f0a3..c7b0752e52181 100644 --- a/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php +++ b/app/code/Magento/Cms/Controller/Adminhtml/Wysiwyg/Images/OnInsert.php @@ -1,62 +1,63 @@ <?php /** - * * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ namespace Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; -class OnInsert extends \Magento\Cms\Controller\Adminhtml\Wysiwyg\Images +use Magento\Backend\App\Action\Context; +use Magento\Cms\Controller\Adminhtml\Wysiwyg\Images; +use Magento\Cms\Model\Wysiwyg\Images\GetInsertImageContent; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\RawFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\Registry; + +class OnInsert extends Images implements HttpPostActionInterface { /** - * @var \Magento\Framework\Controller\Result\RawFactory + * @var RawFactory */ protected $resultRawFactory; /** - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Framework\Registry $coreRegistry - * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory + * @var GetInsertImageContent + */ + private $getInsertImageContent; + + /** + * @param Context $context + * @param Registry $coreRegistry + * @param RawFactory $resultRawFactory + * @param GetInsertImageContent $getInsertImageContent */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\Framework\Registry $coreRegistry, - \Magento\Framework\Controller\Result\RawFactory $resultRawFactory + Context $context, + Registry $coreRegistry, + RawFactory $resultRawFactory, + ?GetInsertImageContent $getInsertImageContent = null ) { $this->resultRawFactory = $resultRawFactory; parent::__construct($context, $coreRegistry); + $this->getInsertImageContent = $getInsertImageContent ?: $this->_objectManager + ->get(GetInsertImageContent::class); } /** - * Fire when select image + * Return a content (just a link or an html block) for inserting image to the content * - * @return \Magento\Framework\Controller\ResultInterface + * @return ResultInterface */ public function execute() { - $imagesHelper = $this->_objectManager->get(\Magento\Cms\Helper\Wysiwyg\Images::class); - $request = $this->getRequest(); - - $storeId = $request->getParam('store'); - - $filename = $request->getParam('filename'); - $filename = $imagesHelper->idDecode($filename); - - $asIs = $request->getParam('as_is'); - - $forceStaticPath = $request->getParam('force_static_path'); - - $this->_objectManager->get(\Magento\Catalog\Helper\Data::class)->setStoreId($storeId); - $imagesHelper->setStoreId($storeId); - - if ($forceStaticPath) { - $image = parse_url($imagesHelper->getCurrentUrl() . $filename, PHP_URL_PATH); - } else { - $image = $imagesHelper->getImageHtmlDeclaration($filename, $asIs); - } - - /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */ - $resultRaw = $this->resultRawFactory->create(); - return $resultRaw->setContents($image); + $data = $this->getRequest()->getParams(); + return $this->resultRawFactory->create()->setContents( + $this->getInsertImageContent->execute( + $data['filename'], + $data['force_static_path'], + $data['as_is'], + isset($data['store']) ? (int) $data['store'] : null + ) + ); } } diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php new file mode 100644 index 0000000000000..305d73fff4dc7 --- /dev/null +++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContent.php @@ -0,0 +1,63 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Model\Wysiwyg\Images; + +use Magento\Catalog\Helper\Data as CatalogHelper; +use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; + +class GetInsertImageContent +{ + /** + * @var ImagesHelper + */ + private $imagesHelper; + + /** + * @var CatalogHelper + */ + private $catalogHelper; + + /** + * @param ImagesHelper $imagesHelper + * @param CatalogHelper $catalogHelper + */ + public function __construct(ImagesHelper $imagesHelper, CatalogHelper $catalogHelper) + { + $this->imagesHelper = $imagesHelper; + $this->catalogHelper = $catalogHelper; + } + + /** + * Create a content (just a link or an html block) for inserting image to the content + * + * @param string $encodedFilename + * @param bool $forceStaticPath + * @param bool $renderAsTag + * @param int|null $storeId + * @return string + */ + public function execute( + string $encodedFilename, + bool $forceStaticPath, + bool $renderAsTag, + ?int $storeId = null + ): string { + $filename = $this->imagesHelper->idDecode($encodedFilename); + + $this->catalogHelper->setStoreId($storeId); + $this->imagesHelper->setStoreId($storeId); + + if ($forceStaticPath) { + // phpcs:ignore Magento2.Functions.DiscouragedFunction + return parse_url($this->imagesHelper->getCurrentUrl() . $filename, PHP_URL_PATH); + } + + return $this->imagesHelper->getImageHtmlDeclaration($filename, $renderAsTag); + } +} diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewCMSPageActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewCMSPageActionGroup.xml index 79ce1bc9d8e47..4e19329e9b899 100644 --- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewCMSPageActionGroup.xml +++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AdminOpenCreateNewCMSPageActionGroup.xml @@ -8,6 +8,10 @@ <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> <actionGroup name="AdminOpenCreateNewCMSPageActionGroup"> + <annotations> + <description>Open create new CMS Page.</description> + </annotations> + <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToCreateNewPage"/> <waitForPageLoad stepKey="waitForNewPagePageLoad"/> </actionGroup> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml index 9163ec4d9f5f8..35d8e692cd460 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToCMSPageTinyMCE3Test.xml @@ -37,8 +37,7 @@ <magentoCLI command="config:set cms/wysiwyg/editor mage/adminhtml/wysiwyg/tiny_mce/tinymce4Adapter" stepKey="enableTinyMCE4"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage2"/> - <waitForPageLoad stepKey="wait5"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage2"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle2"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab2" /> <comment userInput="removing deprecated element" stepKey="waitForTinyMCE3"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml index 55c01f3818a19..698f29a28598f 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddVariableToWYSIWYGCMSTest.xml @@ -31,8 +31,7 @@ <fillField selector="{{StoreConfigSection.City}}" userInput="{{_defaultVariable.city}}" stepKey="fillCity" /> <click selector="{{StoreConfigSection.Save}}" stepKey="saveConfig"/> <!--Main test--> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage"/> - <waitForPageLoad stepKey="waitForPageLoad2"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml index 450003db465a8..509e1abe81ef6 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSPageLinkTypeTest.xml @@ -24,8 +24,7 @@ <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml index 633dd4dbc3388..cfb323683dc2c 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCMSStaticBlockTypeTest.xml @@ -25,8 +25,7 @@ <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml index 14bdc89cec311..d9ea67491e30a 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml @@ -25,8 +25,7 @@ <actionGroup ref="ConfigAdminAccountSharingActionGroup" stepKey="allowAdminShareAccount"/> </before> <!--Main test--> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml index 2b788bc6ca0fd..86f90e0e2a580 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml @@ -28,8 +28,7 @@ <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml index 2124206466c2d..dcb4c3dc11f3c 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductListTypeTest.xml @@ -30,8 +30,7 @@ <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml index 85ae0380d4b43..6acf8ef18a332 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyComparedProductsTypeTest.xml @@ -28,8 +28,7 @@ <actionGroup ref="EnabledWYSIWYGActionGroup" stepKey="enableWYSIWYG"/> <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> </before> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml index 14182a4c33549..1ec4f7054e8c2 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithRecentlyViewedProductsTypeTest.xml @@ -28,8 +28,7 @@ <actionGroup ref="SwitchToVersion4ActionGroup" stepKey="switchToTinyMCE4" /> </before> <!--Main test--> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage"/> - <waitForPageLoad stepKey="wait1"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminConfigDefaultCMSPageLayoutFromConfigurationSettingTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminConfigDefaultCMSPageLayoutFromConfigurationSettingTest.xml index 0eac31c891e64..bc159f2309ab8 100644 --- a/app/code/Magento/Cms/Test/Mftf/Test/AdminConfigDefaultCMSPageLayoutFromConfigurationSettingTest.xml +++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminConfigDefaultCMSPageLayoutFromConfigurationSettingTest.xml @@ -25,8 +25,7 @@ <actionGroup ref="RestoreLayoutSetting" stepKey="sampleActionGroup"/> <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> </after> - <amOnPage url="{{WebConfigurationPage.url}}" stepKey="navigateToWebConfigurationPage"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminOpenWebConfigurationPageActionGroup" stepKey="navigateToWebConfigurationPage"/> <conditionalClick stepKey="expandDefaultLayouts" selector="{{WebSection.DefaultLayoutsTab}}" dependentSelector="{{WebSection.CheckIfTabExpand}}" visible="true" /> <waitForElementVisible selector="{{DefaultLayoutsSection.pageLayout}}" stepKey="DefaultProductLayout" /> <seeOptionIsSelected selector="{{DefaultLayoutsSection.pageLayout}}" userInput="1 column" stepKey="seeOneColumnSelected" /> @@ -34,8 +33,7 @@ <seeOptionIsSelected selector="{{DefaultLayoutsSection.categoryLayout}}" userInput="No layout updates" stepKey="seeNoLayoutUpdatesSelected2" /> <selectOption selector="{{DefaultLayoutsSection.pageLayout}}" userInput="2 columns with right bar" stepKey="selectColumnsWithRightBar"/> <click selector="{{ContentManagementSection.Save}}" stepKey="clickSaveConfig" /> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="amOnPagePagesGrid"/> - <waitForPageLoad stepKey="waitForPageLoad1"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="amOnPagePagesGrid"/> <waitForLoadingMaskToDisappear stepKey="wait2" /> <click selector="{{CmsDesignSection.DesignTab}}" stepKey="clickOnDesignTab"/> <waitForElementVisible selector="{{CmsDesignSection.LayoutDropdown}}" stepKey="waitForLayoutDropDown" /> diff --git a/app/code/Magento/Config/Model/Config/Source/Email/Template.php b/app/code/Magento/Config/Model/Config/Source/Email/Template.php index ac168f16ca182..182faa53e5288 100644 --- a/app/code/Magento/Config/Model/Config/Source/Email/Template.php +++ b/app/code/Magento/Config/Model/Config/Source/Email/Template.php @@ -60,10 +60,12 @@ public function toOptionArray() $this->_coreRegistry->register('config_system_email_template', $collection); } $options = $collection->toOptionArray(); - $templateId = str_replace('/', '_', $this->getPath()); - $templateLabel = $this->_emailConfig->getTemplateLabel($templateId); - $templateLabel = __('%1 (Default)', $templateLabel); - array_unshift($options, ['value' => $templateId, 'label' => $templateLabel]); + if (!empty($this->getPath())) { + $templateId = str_replace('/', '_', $this->getPath()); + $templateLabel = $this->_emailConfig->getTemplateLabel($templateId); + $templateLabel = __('%1 (Default)', $templateLabel); + array_unshift($options, ['value' => $templateId, 'label' => $templateLabel]); + } return $options; } } diff --git a/app/code/Magento/Config/Test/Mftf/Section/AdminEmailToFriendSection.xml b/app/code/Magento/Config/Test/Mftf/Section/AdminEmailToFriendSection.xml new file mode 100644 index 0000000000000..956316ed5cb46 --- /dev/null +++ b/app/code/Magento/Config/Test/Mftf/Section/AdminEmailToFriendSection.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminEmailToFriendSection"> + <element name="DefaultLayoutsTab" type="button" selector=".entry-edit-head-link"/> + <element name="CheckIfTabExpand" type="button" selector=".entry-edit-head-link:not(.open)"/> + <element name="emailTemplate" type="input" selector="#sendfriend_email_template"/> + <element name="allowForGuests" type="input" selector="#sendfriend_email_allow_guest"/> + <element name="maxRecipients" type="input" selector="#sendfriend_email_max_recipients"/> + <element name="maxPerHour" type="input" selector="#sendfriend_email_max_per_hour"/> + <element name="limitSendingBy" type="input" selector="#sendfriend_email_check_by"/> + </section> +</sections> diff --git a/app/code/Magento/Config/Test/Unit/Model/Config/Source/Email/TemplateTest.php b/app/code/Magento/Config/Test/Unit/Model/Config/Source/Email/TemplateTest.php index 356d1133aca81..9b531280f66c6 100644 --- a/app/code/Magento/Config/Test/Unit/Model/Config/Source/Email/TemplateTest.php +++ b/app/code/Magento/Config/Test/Unit/Model/Config/Source/Email/TemplateTest.php @@ -102,4 +102,53 @@ public function testToOptionArray() $this->_model->setPath('template/new'); $this->assertEquals($expectedResult, $this->_model->toOptionArray()); } + + public function testToOptionArrayWithoutPath() + { + $collection = $this->createMock(Collection::class); + $collection->expects( + $this->once() + )->method( + 'toOptionArray' + )->willReturn( + [ + ['value' => 'template_one', 'label' => 'Template One'], + ['value' => 'template_two', 'label' => 'Template Two'], + ] + ); + + $this->_coreRegistry->expects( + $this->once() + )->method( + 'registry' + )->with( + 'config_system_email_template' + )->willReturn( + $collection + ); + + $this->_emailConfig->expects( + $this->never() + )->method( + 'getTemplateLabel' + )->with( + '' + ) + ->willThrowException( + new \UnexpectedValueException("Email template '' is not defined.") + ); + + $expectedResult = [ + [ + 'value' => 'template_one', + 'label' => 'Template One', + ], + [ + 'value' => 'template_two', + 'label' => 'Template Two', + ], + ]; + + $this->assertEquals($expectedResult, $this->_model->toOptionArray()); + } } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php index 9080314126ee1..6031ab6f8f8ae 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Indexer/Price/Configurable.php @@ -221,7 +221,7 @@ private function fillTemporaryOptionsTable(string $temporaryOptionsTableName, ar ['le.entity_id', 'customer_group_id', 'website_id'] ); if ($entityIds !== null) { - $select->where('le.entity_id IN (?)', $entityIds); + $select->where('le.entity_id IN (?)', $entityIds, \Zend_Db::INT_TYPE); } $this->tableMaintainer->insertFromSelect($select, $temporaryOptionsTableName, []); } diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable.php index feffd22a0fb3d..9d779d9704c29 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable.php @@ -173,7 +173,8 @@ public function getChildrenIds($parentId, $required = true) [] )->where( 'p.entity_id IN (?)', - $parentId + $parentId, + \Zend_Db::INT_TYPE ); $childrenIds = [ diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php index 0ced38c4a6923..e4b9acbde3030 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Attribute/Collection.php @@ -41,8 +41,8 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab * Product instance * * @var \Magento\Catalog\Model\Product - * @deprecated 100.3.0 Now collection supports fetching options for multiple products. This field will be set to first - * element of products array. + * @deprecated 100.3.0 Now collection supports fetching options for multiple products. + * This field will be set to first element of products array. */ protected $_product; @@ -286,7 +286,8 @@ protected function _loadLabels() ['use_default' => $useDefaultCheck, 'label' => $labelCheck] )->where( 'def.product_super_attribute_id IN (?)', - array_keys($this->_items) + array_keys($this->_items), + \Zend_Db::INT_TYPE )->where( 'def.store_id = ?', 0 diff --git a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Product/Collection.php b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Product/Collection.php index ae591474cd13e..cefd4b815d729 100644 --- a/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Product/Collection.php +++ b/app/code/Magento/ConfigurableProduct/Model/ResourceModel/Product/Type/Configurable/Product/Collection.php @@ -8,7 +8,7 @@ namespace Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable\Product; /** - * Class Collection + * Collection of configurable product variation * * @api * @SuppressWarnings(PHPMD.CouplingBetweenObjects) @@ -85,7 +85,7 @@ protected function _renderFilters() $parentIds[] = $product->getData($metadata->getLinkField()); } - $this->getSelect()->where('link_table.parent_id in (?)', $parentIds); + $this->getSelect()->where('link_table.parent_id in (?)', $parentIds, \Zend_Db::INT_TYPE); return $this; } diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml index cf0e99f7c45c0..37c129dc3bbde 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml @@ -13,6 +13,7 @@ <element name="selectableProductOptions" type="select" selector="#attribute{{var1}} option:not([disabled])" parameterized="true"/> <element name="productAttributeTitle1" type="text" selector="#product-options-wrapper div[tabindex='0'] label"/> <element name="productPrice" type="text" selector="div.price-box.price-final_price"/> + <element name="tierPriceBlock" type="block" selector="div[data-role='tier-price-block']"/> <element name="productAttributeOptions1" type="select" selector="#product-options-wrapper div[tabindex='0'] option"/> <element name="productAttributeOptionsSelectButton" type="select" selector="#product-options-wrapper .super-attribute-select"/> <element name="productAttributeOptionsError" type="text" selector="//div[@class='mage-error']"/> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml index 1491081a82ee4..4de01b0c9d14e 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml @@ -48,7 +48,7 @@ <!--Add tier price in one product --> <createData entity="tierProductPrice" stepKey="addTierPrice"> - <requiredEntity createDataKey="createFirstSimpleProduct" /> + <requiredEntity createDataKey="createFirstSimpleProduct" /> </createData> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> @@ -109,5 +109,8 @@ <expectedResult type="string">Buy {{tierProductPrice.quantity}} for ${{tierProductPrice.price}} each and save 27%</expectedResult> <actualResult type="variable">tierPriceText</actualResult> </assertEquals> + <seeElement selector="{{StorefrontProductInfoMainSection.tierPriceBlock}}" stepKey="seeTierPriceBlock"/> + <selectOption userInput="$$createConfigProductAttributeOptionTwo.option[store_labels][1][label]$$" selector="{{StorefrontProductInfoMainSection.productAttributeOptionsSelectButton}}" stepKey="selectOption2"/> + <dontSeeElement selector="{{StorefrontProductInfoMainSection.tierPriceBlock}}" stepKey="dontSeeTierPriceBlock"/> </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml index 902787ac58e8c..b87ddf612be19 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/NoOptionAvailableToConfigureDisabledProductTest.xml @@ -176,7 +176,7 @@ <waitForPageLoad stepKey="waitForShippingMethods"/> <click selector="{{AdminInvoicePaymentShippingSection.shippingMethod}}" stepKey="chooseShippingMethod"/> <waitForPageLoad stepKey="waitForShippingMethodLoad"/> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="clickSubmitOrder" /> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="checkOrderSuccessfullyCreated"/> </test> </tests> diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductAddToCartTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductAddToCartTest.xml index 2ca8bbc9feb9d..238f1e107c11b 100644 --- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductAddToCartTest.xml +++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontConfigurableProductViewTest/StorefrontConfigurableProductAddToCartTest.xml @@ -39,8 +39,7 @@ <waitForPageLoad stepKey="wait1"/> <click selector="{{StorefrontCategoryMainSection.modeListButton}}" stepKey="clickListView"/> <waitForPageLoad stepKey="wait2"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="clickAddToCart"/> - <waitForPageLoad stepKey="wait3"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="clickAddToCart"/> <grabFromCurrentUrl stepKey="grabUrl"/> <assertStringContainsString stepKey="assertUrl"> <expectedResult type="string">{{_defaultProduct.urlKey}}</expectedResult> diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js index 68e7d146d33e0..814b5de71a8f7 100644 --- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.js @@ -219,7 +219,6 @@ define([ _.each(tmpData, function (row, index) { path = this.dataScope + '.' + this.index + '.' + (this.startIndex + index); row.attributes = $('<i></i>').text(row.attributes).html(); - row.sku = row.sku; this.source.set(path, row); }, this); @@ -227,11 +226,11 @@ define([ this.parsePagesData(data); // Render - dataCount = data.length; + dataCount = tmpData.length; elemsCount = this.elems().length; if (dataCount > elemsCount) { - this.getChildItems().each(function (elemData, index) { + tmpData.each(function (elemData, index) { this.addChild(elemData, this.startIndex + index); }, this); } else { @@ -243,6 +242,15 @@ define([ this.generateAssociatedProducts(); }, + /** + * Set initial property to records data + * + * @returns {Object} Chainable. + */ + setInitialProperty: function () { + return this; + }, + /** * Parsed data * diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js index f705b6a95987c..00030be74324f 100644 --- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js +++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js @@ -740,21 +740,19 @@ define([ * @private */ _displayTierPriceBlock: function (optionId) { - var options, tierPriceHtml; + var tierPrices = typeof optionId != 'undefined' && this.options.spConfig.optionPrices[optionId].tierPrices; - if (typeof optionId != 'undefined' && - this.options.spConfig.optionPrices[optionId].tierPrices != [] // eslint-disable-line eqeqeq - ) { - options = this.options.spConfig.optionPrices[optionId]; + if (_.isArray(tierPrices) && tierPrices.length > 0) { if (this.options.tierPriceTemplate) { - tierPriceHtml = mageTemplate(this.options.tierPriceTemplate, { - 'tierPrices': options.tierPrices, - '$t': $t, - 'currencyFormat': this.options.spConfig.currencyFormat, - 'priceUtils': priceUtils - }); - $(this.options.tierPriceBlockSelector).html(tierPriceHtml).show(); + $(this.options.tierPriceBlockSelector).html( + mageTemplate(this.options.tierPriceTemplate, { + 'tierPrices': tierPrices, + '$t': $t, + 'currencyFormat': this.options.spConfig.currencyFormat, + 'priceUtils': priceUtils + }) + ).show(); } } else { $(this.options.tierPriceBlockSelector).hide(); diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Wishlist/ChildSku.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Wishlist/ChildSku.php new file mode 100644 index 0000000000000..84decab81c96a --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Wishlist/ChildSku.php @@ -0,0 +1,43 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProductGraphQl\Model\Wishlist; + +use Magento\Catalog\Model\Product; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Fetches the simple child sku of configurable product + */ +class ChildSku implements ResolverInterface +{ + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$value['model'] instanceof Product) { + throw new LocalizedException(__('"itemModel" should be a "%instance" instance', [ + 'instance' => Product::class + ])); + } + + /** @var Product $product */ + $product = $value['model']; + $optionProduct = $product->getCustomOption('simple_product')->getProduct(); + + return $optionProduct->getSku(); + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Wishlist/ConfigurableOptions.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Wishlist/ConfigurableOptions.php new file mode 100644 index 0000000000000..6fcb3e118e5f1 --- /dev/null +++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Wishlist/ConfigurableOptions.php @@ -0,0 +1,67 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\ConfigurableProductGraphQl\Model\Wishlist; + +use Magento\Catalog\Helper\Product\Configuration; +use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Fetches the selected configurable options + */ +class ConfigurableOptions implements ResolverInterface +{ + /** + * @var Configuration + */ + private $configurationHelper; + + /** + * @param Configuration $configurationHelper + */ + public function __construct( + Configuration $configurationHelper + ) { + $this->configurationHelper = $configurationHelper; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$value['itemModel'] instanceof ItemInterface) { + throw new LocalizedException(__('"itemModel" should be a "%instance" instance', [ + 'instance' => ItemInterface::class + ])); + } + + /** @var ItemInterface $item */ + $item = $value['itemModel']; + $result = []; + + foreach ($this->configurationHelper->getOptions($item) as $option) { + $result[] = [ + 'id' => $option['option_id'], + 'option_label' => $option['label'], + 'value_id' => $option['option_value'], + 'value_label' => $option['value'], + ]; + } + + return $result; + } +} diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml index f82bb0dbd4d91..808ca62f7e149 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/graphql/di.xml @@ -36,4 +36,11 @@ </argument> </arguments> </type> + <type name="Magento\WishlistGraphQl\Model\Resolver\Type\WishlistItemType"> + <arguments> + <argument name="supportedTypes" xsi:type="array"> + <item name="configurable" xsi:type="string">ConfigurableWishlistItem</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls index 6e85653380acc..257bca11fb5b7 100644 --- a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls +++ b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls @@ -68,3 +68,8 @@ type SelectedConfigurableOption { value_id: Int! value_label: String! } + +type ConfigurableWishlistItem implements WishlistItemInterface @doc(description: "A configurable product wish list item"){ + child_sku: String! @doc(description: "The SKU of the simple product corresponding to a set of selected configurable options") @resolver(class: "\\Magento\\ConfigurableProductGraphQl\\Model\\Wishlist\\ChildSku") + configurable_options: [SelectedConfigurableOption!] @resolver(class: "\\Magento\\ConfigurableProductGraphQl\\Model\\Wishlist\\ConfigurableOptions") @doc (description: "An array of selected configurable options") +} diff --git a/app/code/Magento/Csp/etc/csp_whitelist.xml b/app/code/Magento/Csp/etc/csp_whitelist.xml new file mode 100644 index 0000000000000..b0cce028ac8b6 --- /dev/null +++ b/app/code/Magento/Csp/etc/csp_whitelist.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<csp_whitelist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Csp:etc/csp_whitelist.xsd"> + <policies> + <policy id="img-src"> + <values> + <value id="data" type="host">data:</value> + </values> + </policy> + </policies> +</csp_whitelist> diff --git a/app/code/Magento/Customer/Model/Customer/Authorization.php b/app/code/Magento/Customer/Model/Customer/Authorization.php new file mode 100644 index 0000000000000..5df3dbc51b732 --- /dev/null +++ b/app/code/Magento/Customer/Model/Customer/Authorization.php @@ -0,0 +1,82 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\Customer; + +use Magento\Authorization\Model\UserContextInterface; +use Magento\Customer\Model\CustomerFactory; +use Magento\Customer\Model\ResourceModel\Customer as CustomerResource; +use Magento\Framework\AuthorizationInterface; +use Magento\Integration\Api\AuthorizationServiceInterface as AuthorizationService; +use Magento\Store\Model\StoreManagerInterface; + +/** + * Checks if customer is logged in and authorized in the current store + */ +class Authorization implements AuthorizationInterface +{ + /** + * @var UserContextInterface + */ + private $userContext; + + /** + * @var CustomerFactory + */ + private $customerFactory; + + /** + * @var CustomerResource + */ + private $customerResource; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * Authorization constructor. + * + * @param UserContextInterface $userContext + * @param CustomerFactory $customerFactory + * @param CustomerResource $customerResource + * @param StoreManagerInterface $storeManager + */ + public function __construct( + UserContextInterface $userContext, + CustomerFactory $customerFactory, + CustomerResource $customerResource, + StoreManagerInterface $storeManager + ) { + $this->userContext = $userContext; + $this->customerFactory = $customerFactory; + $this->customerResource = $customerResource; + $this->storeManager = $storeManager; + } + + /** + * @inheritdoc + */ + public function isAllowed($resource, $privilege = null) + { + if ($resource === AuthorizationService::PERMISSION_SELF + && $this->userContext->getUserId() + && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER + ) { + $customer = $this->customerFactory->create(); + $this->customerResource->load($customer, $this->userContext->getUserId()); + $currentStoreId = $this->storeManager->getStore()->getId(); + $sharedStoreIds = $customer->getSharedStoreIds(); + + return in_array($currentStoreId, $sharedStoreIds); + } + + return false; + } +} diff --git a/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php new file mode 100644 index 0000000000000..716719470796e --- /dev/null +++ b/app/code/Magento/Customer/Model/Customer/AuthorizationComposite.php @@ -0,0 +1,50 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\Customer; + +use Magento\Framework\AuthorizationInterface; + +/** + * Class to invalidate user credentials + */ +class AuthorizationComposite implements AuthorizationInterface +{ + /** + * @var AuthorizationInterface[] + */ + private $authorizationChecks; + + /** + * AuthorizationComposite constructor. + * + * @param AuthorizationInterface[] $authorizationChecks + */ + public function __construct( + array $authorizationChecks + ) { + $this->authorizationChecks = $authorizationChecks; + } + + /** + * @inheritdoc + */ + public function isAllowed($resource, $privilege = null) + { + $result = false; + + foreach ($this->authorizationChecks as $authorizationCheck) { + $result = $authorizationCheck->isAllowed($resource, $privilege); + if (!$result) { + break; + } + } + + return $result; + } +} diff --git a/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php b/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php index 09af4e296bd92..211a71d827f7e 100644 --- a/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php +++ b/app/code/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByToken.php @@ -73,7 +73,7 @@ public function execute(string $resetPasswordToken):CustomerInterface } if ($found->getTotalCount() === 0) { //Customer with such token not found. - new NoSuchEntityException( + throw new NoSuchEntityException( new Phrase( 'No such entity with rp_token = %value', [ diff --git a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php index b877b2cca67a5..271d8f795d6f6 100644 --- a/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php +++ b/app/code/Magento/Customer/Model/Plugin/CustomerAuthorization.php @@ -6,11 +6,9 @@ namespace Magento\Customer\Model\Plugin; -use Magento\Authorization\Model\UserContextInterface; -use Magento\Customer\Model\CustomerFactory; -use Magento\Customer\Model\ResourceModel\Customer as CustomerResource; -use Magento\Integration\Api\AuthorizationServiceInterface as AuthorizationService; -use Magento\Store\Model\StoreManagerInterface; +use Closure; +use Magento\Customer\Model\Customer\AuthorizationComposite; +use Magento\Framework\Authorization; /** * Plugin around \Magento\Framework\Authorization::isAllowed @@ -20,74 +18,38 @@ class CustomerAuthorization { /** - * @var UserContextInterface + * @var AuthorizationComposite */ - private $userContext; - - /** - * @var CustomerFactory - */ - private $customerFactory; - - /** - * @var CustomerResource - */ - private $customerResource; - - /** - * @var StoreManagerInterface - */ - private $storeManager; + private $authorizationComposite; /** * Inject dependencies. - * - * @param UserContextInterface $userContext - * @param CustomerFactory $customerFactory - * @param CustomerResource $customerResource - * @param StoreManagerInterface $storeManager + * @param AuthorizationComposite $composite */ public function __construct( - UserContextInterface $userContext, - CustomerFactory $customerFactory, - CustomerResource $customerResource, - StoreManagerInterface $storeManager + AuthorizationComposite $composite ) { - $this->userContext = $userContext; - $this->customerFactory = $customerFactory; - $this->customerResource = $customerResource; - $this->storeManager = $storeManager; + $this->authorizationComposite = $composite; } /** - * Check if resource for which access is needed has self permissions defined in webapi config. + * Verify if to allow customer users to access resources with self permission * - * @param \Magento\Framework\Authorization $subject - * @param callable $proceed - * @param string $resource - * @param string $privilege - * - * @return bool true If resource permission is self, to allow - * customer access without further checks in parent method * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param Authorization $subject + * @param Closure $proceed + * @param string $resource + * @param mixed $privilege + * @return bool */ public function aroundIsAllowed( - \Magento\Framework\Authorization $subject, - \Closure $proceed, - $resource, + Authorization $subject, + Closure $proceed, + string $resource, $privilege = null ) { - if ($resource == AuthorizationService::PERMISSION_SELF - && $this->userContext->getUserId() - && $this->userContext->getUserType() === UserContextInterface::USER_TYPE_CUSTOMER - ) { - $customer = $this->customerFactory->create(); - $this->customerResource->load($customer, $this->userContext->getUserId()); - $currentStoreId = $this->storeManager->getStore()->getId(); - $sharedStoreIds = $customer->getSharedStoreIds(); - if (in_array($currentStoreId, $sharedStoreIds)) { - return true; - } + if ($this->authorizationComposite->isAllowed($resource, $privilege)) { + return true; } return $proceed($resource, $privilege); diff --git a/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php b/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php index 41311abee5da8..fd5004ae0548f 100644 --- a/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php +++ b/app/code/Magento/Customer/Observer/AfterAddressSaveObserver.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Customer\Observer; @@ -17,6 +18,7 @@ use Magento\Framework\App\State as AppState; use Magento\Framework\DataObject; use Magento\Framework\Escaper; +use Magento\Framework\Event\Observer; use Magento\Framework\Event\ObserverInterface; use Magento\Framework\Message\ManagerInterface; use Magento\Framework\Registry; @@ -25,6 +27,7 @@ /** * Customer Observer Model * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) */ class AfterAddressSaveObserver implements ObserverInterface { @@ -114,11 +117,11 @@ public function __construct( /** * Address after save event handler * - * @param \Magento\Framework\Event\Observer $observer + * @param Observer $observer * @return void * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function execute(\Magento\Framework\Event\Observer $observer) + public function execute(Observer $observer) { /** @var $customerAddress Address */ $customerAddress = $observer->getCustomerAddress(); @@ -280,7 +283,7 @@ protected function addInvalidMessage($customerAddress) $message[] = (string)__('You will be charged tax.'); } - $this->messageManager->addError(implode(' ', $message)); + $this->messageManager->addErrorMessage(implode(' ', $message)); return $this; } @@ -307,7 +310,7 @@ protected function addErrorMessage($customerAddress) $email = $this->scopeConfig->getValue('trans_email/ident_support/email', ScopeInterface::SCOPE_STORE); $message[] = (string)__('If you believe this is an error, please contact us at %1', $email); - $this->messageManager->addError(implode(' ', $message)); + $this->messageManager->addErrorMessage(implode(' ', $message)); return $this; } diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminClickFirstRowEditLinkOnCustomerGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminClickFirstRowEditLinkOnCustomerGridActionGroup.xml new file mode 100644 index 0000000000000..0cfe9f80d1619 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminClickFirstRowEditLinkOnCustomerGridActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminClickFirstRowEditLinkOnCustomerGridActionGroup"> + <annotations> + <description>Click edit link for first row on the grid.</description> + </annotations> + + <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditLink"/> + <waitForPageLoad stepKey="waitForPageLoading"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml index bbdc4de330840..cd581ed1836dd 100644 --- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerFindWishlistItemActionGroup.xml @@ -14,6 +14,6 @@ </arguments> <fillField userInput="{{productName}}" selector="{{AdminCustomerWishlistSection.productName}}" stepKey="fillProductNameField"/> <click selector="{{AdminCustomerWishlistSection.searchButton}}" stepKey="clickSearchButton"/> - <waitForPageLoad stepKey="waitForGridLoading"/> + <waitForAjaxLoad time="60" stepKey="waitForLoading"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerLogoutSuccessPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerLogoutSuccessPageActionGroup.xml new file mode 100644 index 0000000000000..495f4504fcfd5 --- /dev/null +++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontCustomerLogoutSuccessPageActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertStorefrontCustomerLogoutSuccessPageActionGroup"> + <annotations> + <description>Assert on the Storefront Customer Logout Success Page page.</description> + </annotations> + + <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" stepKey="seeOnSignInPage"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml index 695fc138e592b..e31be78185aaf 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml @@ -19,7 +19,7 @@ <item>Bld D</item> </array> <data key="company">Magento</data> - <data key="telephone">123-456-7890</data> + <data key="telephone">1234568910</data> <data key="fax">1234568910</data> <data key="postcode">78729</data> <data key="city">Austin</data> @@ -172,7 +172,7 @@ <data key="city">London</data> <data key="postcode">SE1 7RW</data> <data key="country_id">GB</data> - <data key="telephone">444-444-4444</data> + <data key="telephone">444-44-444-44</data> </entity> <entity name="US_Address_Utah" type="address"> <data key="firstname">John</data> @@ -227,7 +227,7 @@ <data key="firstname">John</data> <data key="lastname">Doe</data> <data key="company">Magento</data> - <data key="telephone">888-777-7890</data> + <data key="telephone">0123456789-02134567</data> <array key="street"> <item>172, Westminster Bridge Rd</item> <item>7700 xyz street</item> @@ -305,7 +305,7 @@ <data key="firstname">Jane</data> <data key="lastname">Miller</data> <data key="company">Magento</data> - <data key="telephone">123-456-7899</data> + <data key="telephone">44 20 7123 1234</data> <array key="street"> <item>1 London Bridge Street</item> </array> diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml index e176c45a1fa00..5db0b8f5581d7 100644 --- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml +++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml @@ -285,6 +285,21 @@ <requiredEntity type="address">DE_Address_Berlin_Not_Default_Address</requiredEntity> <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> </entity> + <entity name="Customer_DE_UK_US" type="customer"> + <data key="group_id">1</data> + <data key="default_billing">true</data> + <data key="default_shipping">true</data> + <data key="email" unique="prefix">John.Doe@example.com</data> + <data key="firstname">John</data> + <data key="lastname">Doe</data> + <data key="fullname">John Doe</data> + <data key="password">pwdTest123!</data> + <data key="store_id">0</data> + <data key="website_id">0</data> + <requiredEntity type="address">DE_Address_Berlin_Not_Default_Address</requiredEntity> + <requiredEntity type="address">UK_Not_Default_Address</requiredEntity> + <requiredEntity type="address">US_Address_NY</requiredEntity> + </entity> <entity name="Retailer_Customer" type="customer"> <data key="group_id">3</data> <data key="default_billing">true</data> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml index c8e3bc10cc769..9f6d8d645e5f4 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml @@ -50,8 +50,7 @@ <see userInput="{{CustomerEntityOne.email}}" selector="{{AdminCustomerGridSection.customerGrid}}" stepKey="assertEmail"/> <!--Assert Customer Form --> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton1"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad1"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton1"/> <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="clickOnAccountInformation"/> <waitForPageLoad stepKey="waitForCustomerInformationPageToLoad"/> <see selector="{{AdminCustomerAccountInformationSection.groupIdValue}}" userInput="Retailer" stepKey="seeCustomerGroup1"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryPolandTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryPolandTest.xml index 5f496e2c5fba3..782c1599bf489 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryPolandTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryPolandTest.xml @@ -31,8 +31,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterTheCustomerByEmail"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton"/> <!-- Add the Address --> <click selector="{{AdminEditCustomerAddressesSection.addresses}}" stepKey="selectAddress"/> @@ -67,8 +66,7 @@ <see userInput="{{PolandAddress.telephone}}" selector="{{AdminCustomerGridSection.customerGrid}}" stepKey="assertPhoneNumber"/> <!--Assert Customer Form --> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton1"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad1"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton1"/> <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="clickOnAccountInformation"/> <waitForPageLoad stepKey="waitForCustomerInformationPageToLoad"/> <seeInField selector="{{AdminCustomerAccountInformationSection.firstName}}" userInput="$$createCustomer.firstname$$" stepKey="seeCustomerFirstName"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml index da2eed2006434..304d545fb4c93 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml @@ -31,8 +31,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterTheCustomerByEmail"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton"/> <!-- Add the Address --> <click selector="{{AdminEditCustomerAddressesSection.addresses}}" stepKey="selectAddress"/> @@ -67,8 +66,7 @@ <see userInput="{{US_Address_CA.telephone}}" selector="{{AdminCustomerGridSection.customerGrid}}" stepKey="assertPhoneNumber"/> <!--Assert Customer Form --> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton1"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad1"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton1"/> <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="clickOnAccountInformation"/> <waitForPageLoad stepKey="waitForCustomerInformationPageToLoad"/> <seeInField selector="{{AdminCustomerAccountInformationSection.firstName}}" userInput="$$createCustomer.firstname$$" stepKey="seeCustomerFirstName"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml index 8afd1648d26e0..7cffd5f304e31 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml @@ -54,8 +54,7 @@ <see userInput="{{CustomerEntityOne.email}}" selector="{{AdminCustomerGridSection.customerGrid}}" stepKey="assertEmail"/> <!--Assert Customer Form --> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton1"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad1"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton1"/> <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="clickOnAccountInformation"/> <waitForPageLoad stepKey="waitForCustomerInformationPageToLoad"/> <see selector="{{AdminCustomerAccountInformationSection.groupIdValue}}" userInput="$$customerGroup.code$$" stepKey="seeCustomerGroup1"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml index e9250be637534..eaa3a11edb74e 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml @@ -56,8 +56,7 @@ <see userInput="Male" selector="{{AdminCustomerGridSection.customerGrid}}" stepKey="assertGender"/> <!--Assert Customer Form --> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton1"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad1"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton1"/> <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="clickOnAccountInformation"/> <waitForPageLoad stepKey="waitForCustomerInformationPageToLoad"/> <seeInField selector="{{AdminCustomerAccountInformationSection.namePrefix}}" userInput="{{CustomerEntityOne.prefix}}" stepKey="seeCustomerNamePrefix"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml index 5033f2882af42..98826b147ad81 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml @@ -49,8 +49,7 @@ <see userInput="{{CustomerEntityOne.email}}" selector="{{AdminCustomerGridSection.customerGrid}}" stepKey="assertEmail"/> <!--Assert Customer Form --> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton1"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad1"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton1"/> <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="clickOnAccountInformation"/> <waitForPageLoad stepKey="waitForCustomerInformationPageToLoad"/> <seeInField selector="{{AdminCustomerAccountInformationSection.firstName}}" userInput="{{CustomerEntityOne.firstname}}" stepKey="seeCustomerFirstName"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml index 5440339e3a95e..683b275ca1ed6 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml @@ -45,8 +45,7 @@ <see selector="{{AdminCustomerGridSection.customerGrid}}" userInput="{{CustomerEntityOne.email}}" stepKey="seeAssertCustomerEmailInGrid"/> <!--Assert verify created new customer is subscribed to newsletter--> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickFirstRowEditLink"/> - <waitForPageLoad stepKey="waitForEditLinkLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickFirstRowEditLink"/> <click selector="{{AdminEditCustomerInformationSection.newsLetter}}" stepKey="clickNewsLetter"/> <waitForPageLoad stepKey="waitForNewsletterTabToOpen"/> <seeCheckboxIsChecked selector="{{AdminEditCustomerNewsletterSection.subscribedStatus('1')}}" stepKey="seeAssertSubscribedToNewsletterCheckboxIsChecked"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml index 6b484e857d276..5edb9d08da46d 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml @@ -42,8 +42,7 @@ <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> <waitForPageLoad stepKey="waitForPageToLoad"/> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickOnEditButton1"/> - <waitForPageLoad stepKey="waitForCustomerEditPageToLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickOnEditButton1"/> <!-- Assert Customer Title --> <click selector="{{AdminCustomerAccountInformationSection.accountInformationButton}}" stepKey="clickOnAccountInformation"/> diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomerSubscribeNewsletterPerWebsiteTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomerSubscribeNewsletterPerWebsiteTest.xml index a8391458a1a50..87111ec6fba1a 100644 --- a/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomerSubscribeNewsletterPerWebsiteTest.xml +++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCustomerSubscribeNewsletterPerWebsiteTest.xml @@ -53,8 +53,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterCustomerGrid"> <argument name="email" value="{{CustomerEntityOne.email}}"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickToEditCustomerPage"/> - <waitForPageLoad stepKey="waitForOpenCustomerPage"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickToEditCustomerPage"/> <grabFromCurrentUrl regex="~(\d+)/~" stepKey="grabCustomerId"/> <!-- Assert that created customer is subscribed to newsletter on the new Store View --> <actionGroup ref="AdminAssertCustomerIsSubscribedToNewslettersAndSelectedStoreView" stepKey="assertSubscribedToNewsletter"> diff --git a/app/code/Magento/Customer/Test/Unit/Model/ForgotPasswordToken/GetCustomerByTokenTest.php b/app/code/Magento/Customer/Test/Unit/Model/ForgotPasswordToken/GetCustomerByTokenTest.php new file mode 100644 index 0000000000000..67dbb136297ff --- /dev/null +++ b/app/code/Magento/Customer/Test/Unit/Model/ForgotPasswordToken/GetCustomerByTokenTest.php @@ -0,0 +1,124 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Test\Unit\Model\ForgotPasswordToken; + +use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Api\Data\CustomerInterface; +use Magento\Customer\Api\Data\CustomerSearchResultsInterface; +use Magento\Customer\Model\ForgotPasswordToken\GetCustomerByToken; +use Magento\Framework\Api\SearchCriteria; +use Magento\Framework\Api\SearchCriteriaBuilder; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\State\ExpiredException; +use Magento\Framework\Phrase; +use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; + +class GetCustomerByTokenTest extends TestCase +{ + private const RESET_PASSWORD = 'resetPassword'; + + /** + * @var SearchCriteriaBuilder|MockObject + */ + private $searchCriteriaBuilderMock; + + /** + * @var SearchCriteria|MockObject + */ + private $searchCriteriaMock; + + /** + * @var CustomerRepositoryInterface|MockObject + */ + private $customerRepositoryMock; + + /** + * @var CustomerSearchResultsInterface|MockObject + */ + private $searchResultMock; + + /** + * @var CustomerInterface|MockObject + */ + private $customerMock; + + /** + * @var GetCustomerByToken; + */ + private $model; + + protected function setUp(): void + { + $this->searchCriteriaBuilderMock = $this->createMock(SearchCriteriaBuilder::class); + $this->searchCriteriaMock = $this->createMock(SearchCriteria::class); + $this->searchResultMock = $this->createMock(CustomerSearchResultsInterface::class); + $this->customerRepositoryMock = $this->createMock(CustomerRepositoryInterface::class); + $this->customerMock = $this->getMockForAbstractClass(CustomerInterface::class); + + $objectManagerHelper = new ObjectManagerHelper($this); + $this->model = $objectManagerHelper->getObject( + GetCustomerByToken::class, + [ + 'searchCriteriaBuilder' => $this->searchCriteriaBuilderMock, + 'customerRepository' => $this->customerRepositoryMock + ] + ); + + $this->searchCriteriaBuilderMock->expects($this->once()) + ->method('create') + ->willReturn($this->searchCriteriaMock); + $this->customerRepositoryMock->expects($this->once()) + ->method('getList') + ->with($this->searchCriteriaMock) + ->willReturn($this->searchResultMock); + } + + public function testExecuteReturnWhenOneItemAvailable(): void + { + $totalCount = 1; + $this->searchResultMock->method('getTotalCount')->willReturn($totalCount); + $this->searchResultMock->expects($this->once()) + ->method('getItems') + ->willReturn([$this->customerMock]); + + $this->assertInstanceOf( + CustomerInterface::class, + $this->model->execute(self::RESET_PASSWORD) + ); + } + + public function testExecuteWithNoSuchEntityException(): void + { + $totalCount = 0; + $this->searchResultMock->method('getTotalCount')->willReturn($totalCount); + $this->expectExceptionObject(new NoSuchEntityException( + new Phrase( + 'No such entity with rp_token = %value', + ['value' => self::RESET_PASSWORD] + ) + )); + + $this->model->execute(self::RESET_PASSWORD); + } + + public function testExecuteWithExpireException(): void + { + $totalCount = 2; + $this->searchResultMock->method('getTotalCount')->willReturn($totalCount); + + $this->expectExceptionObject(new ExpiredException( + new Phrase( + 'Reset password token expired.' + ) + )); + + $this->model->execute(self::RESET_PASSWORD); + } +} diff --git a/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php b/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php index 7232317af8ade..f72cbbc281e90 100644 --- a/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php +++ b/app/code/Magento/Customer/Test/Unit/Observer/AfterAddressSaveObserverTest.php @@ -80,29 +80,23 @@ class AfterAddressSaveObserverTest extends TestCase protected $appState; /** - * @var Customer|MockObject + * @var Session|MockObject */ - protected $customerMock; + protected $customerSessionMock; /** - * @var Session|MockObject + * @var GroupInterface|MockObject */ - protected $customerSessionMock; + protected $group; protected function setUp(): void { - $this->vat = $this->getMockBuilder(Vat::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->helperAddress = $this->getMockBuilder(\Magento\Customer\Helper\Address::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->registry = $this->getMockBuilder(Registry::class) - ->disableOriginalConstructor() - ->getMock(); - + $this->vat = $this->createMock(Vat::class); + $this->helperAddress = $this->createMock(HelperAddress::class); + $this->registry = $this->createMock(Registry::class); + $this->escaper = $this->createMock(Escaper::class); + $this->appState = $this->createMock(AppState::class); + $this->customerSessionMock = $this->createMock(Session::class); $this->group = $this->getMockBuilder(GroupInterface::class) ->setMethods(['getId']) ->getMockForAbstractClass(); @@ -114,22 +108,9 @@ protected function setUp(): void $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class) ->getMockForAbstractClass(); - $this->messageManager = $this->getMockBuilder(ManagerInterface::class) ->getMockForAbstractClass(); - $this->escaper = $this->getMockBuilder(Escaper::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->appState = $this->getMockBuilder(\Magento\Framework\App\State::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->customerSessionMock = $this->getMockBuilder(Session::class) - ->disableOriginalConstructor() - ->getMock(); - $this->model = new AfterAddressSaveObserver( $this->vat, $this->helperAddress, @@ -595,7 +576,7 @@ public function testAfterAddressSaveNewGroup( ->with($vatId) ->willReturn($vatId); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($resultInvalidMessage) ->willReturnSelf(); } @@ -605,7 +586,7 @@ public function testAfterAddressSaveNewGroup( ->with('trans_email/ident_support/email', ScopeInterface::SCOPE_STORE) ->willReturn('admin@example.com'); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($resultErrorMessage) ->willReturnSelf(); } diff --git a/app/code/Magento/Customer/etc/webapi_rest/di.xml b/app/code/Magento/Customer/etc/webapi_rest/di.xml index a349d07a5e222..d07d1a61c3d62 100644 --- a/app/code/Magento/Customer/etc/webapi_rest/di.xml +++ b/app/code/Magento/Customer/etc/webapi_rest/di.xml @@ -22,4 +22,13 @@ <type name="Magento\Customer\Api\CustomerRepositoryInterface"> <plugin name="updateCustomerByIdFromRequest" type="Magento\Customer\Model\Plugin\UpdateCustomer" /> </type> + <type name="Magento\Customer\Model\Customer\AuthorizationComposite"> + <arguments> + <argument name="authorizationChecks" xsi:type="array"> + <item name="rest_customer_authorization" xsi:type="object"> + Magento\Customer\Model\Customer\Authorization + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Customer/etc/webapi_soap/di.xml b/app/code/Magento/Customer/etc/webapi_soap/di.xml index 646ba98b4c5d8..c23de8ef3f7e1 100644 --- a/app/code/Magento/Customer/etc/webapi_soap/di.xml +++ b/app/code/Magento/Customer/etc/webapi_soap/di.xml @@ -9,4 +9,13 @@ <type name="Magento\Framework\Authorization"> <plugin name="customerAuthorization" type="Magento\Customer\Model\Plugin\CustomerAuthorization" /> </type> + <type name="Magento\Customer\Model\Customer\AuthorizationComposite"> + <arguments> + <argument name="authorizationChecks" xsi:type="array"> + <item name="soap_customer_authorization" xsi:type="object"> + Magento\Customer\Model\Customer\Authorization + </item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js index b8804cd5251a8..d50541504662d 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/customer-data.js +++ b/app/code/Magento/Customer/view/frontend/web/js/customer-data.js @@ -261,6 +261,9 @@ define([ } }); + //remove expired section names of previously installed/enable modules + expiredSectionNames = _.intersection(expiredSectionNames, sectionConfig.getSectionNames()); + return _.uniq(expiredSectionNames); }, diff --git a/app/code/Magento/Customer/view/frontend/web/js/validation.js b/app/code/Magento/Customer/view/frontend/web/js/validation.js index 1f7f24d5ac031..9ffc8137135da 100644 --- a/app/code/Magento/Customer/view/frontend/web/js/validation.js +++ b/app/code/Magento/Customer/view/frontend/web/js/validation.js @@ -8,6 +8,20 @@ define([ ], function ($, moment, utils) { 'use strict'; + $.validator.addMethod( + 'validate-date', + function (value, element, params) { + var dateFormat = utils.convertToMomentFormat(params.dateFormat); + + if (value === '') { + return true; + } + + return moment(value, dateFormat, true).isValid(); + }, + $.mage.__('Invalid date') + ); + $.validator.addMethod( 'validate-dob', function (value, element, params) { diff --git a/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php b/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php index 5800f7162b7f0..15f9031c2c769 100644 --- a/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php +++ b/app/code/Magento/Developer/Console/Command/GeneratePatchCommand.php @@ -133,6 +133,9 @@ protected function execute(InputInterface $input, OutputInterface $output): int } $type = $input->getOption(self::INPUT_KEY_PATCH_TYPE); $modulePath = $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, $moduleName); + if (null === $modulePath) { + throw new \InvalidArgumentException(sprintf('Cannot find a registered module with name "%s"', $moduleName)); + } $preparedModuleName = str_replace('_', '\\', $moduleName); $preparedType = ucfirst($type); $patchInterface = sprintf('%sPatchInterface', $preparedType); diff --git a/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php b/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php new file mode 100644 index 0000000000000..6fa1ca8a4674a --- /dev/null +++ b/app/code/Magento/Developer/Test/Unit/Console/Command/GeneratePatchCommandTest.php @@ -0,0 +1,119 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +namespace Magento\Developer\Test\Unit\Console\Command; + +use Magento\Developer\Console\Command\GeneratePatchCommand; +use Magento\Framework\Component\ComponentRegistrar; +use Magento\Framework\Filesystem\Directory\Read; +use Magento\Framework\Filesystem\Directory\ReadFactory; +use Magento\Framework\Filesystem\Directory\Write; +use Magento\Framework\Filesystem\Directory\WriteFactory; +use Magento\Framework\Filesystem\DirectoryList; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Symfony\Component\Console\Tester\CommandTester; + +class GeneratePatchCommandTest extends TestCase +{ + /** + * @var ComponentRegistrar|MockObject + */ + private $componentRegistrarMock; + + /** + * @var DirectoryList|MockObject + */ + private $directoryListMock; + + /** + * @var ReadFactory|MockObject + */ + private $readFactoryMock; + + /** + * @var WriteFactory|MockObject + */ + private $writeFactoryMock; + + /** + * @var GeneratePatchCommand|MockObject + */ + private $command; + + protected function setUp(): void + { + $this->componentRegistrarMock = $this->createMock(ComponentRegistrar::class); + $this->directoryListMock = $this->createMock(DirectoryList::class); + $this->readFactoryMock = $this->createMock(ReadFactory::class); + $this->writeFactoryMock = $this->createMock(WriteFactory::class); + + $this->command = new GeneratePatchCommand( + $this->componentRegistrarMock, + $this->directoryListMock, + $this->readFactoryMock, + $this->writeFactoryMock + ); + } + + public function testExecute() + { + $this->componentRegistrarMock->expects($this->once()) + ->method('getPath') + ->with('module', 'Vendor_Module') + ->willReturn('/long/path/to/Vendor/Module'); + + $read = $this->createMock(Read::class); + $read->expects($this->at(0)) + ->method('readFile') + ->with('patch_template.php.dist') + ->willReturn('something'); + $this->readFactoryMock->method('create')->willReturn($read); + + $write = $this->createMock(Write::class); + $write->expects($this->once())->method('writeFile'); + $this->writeFactoryMock->method('create')->willReturn($write); + + $this->directoryListMock->expects($this->once())->method('getRoot')->willReturn('/some/path'); + + $commandTester = new CommandTester($this->command); + $commandTester->execute( + [ + GeneratePatchCommand::MODULE_NAME => 'Vendor_Module', + GeneratePatchCommand::INPUT_KEY_PATCH_NAME => 'SomePatch' + ] + ); + $this->assertStringContainsString('successfully generated', $commandTester->getDisplay()); + } + + public function testWrongParameter() + { + $this->expectExceptionMessage('Not enough arguments'); + $this->expectException(\RuntimeException::class); + + $commandTester = new CommandTester($this->command); + $commandTester->execute([]); + } + + public function testBadModule() + { + $this->componentRegistrarMock->expects($this->once()) + ->method('getPath') + ->with('module', 'Fake_Module') + ->willReturn(null); + + $this->expectExceptionMessage('Cannot find a registered module with name "Fake_Module"'); + $this->expectException(\InvalidArgumentException::class); + + $commandTester = new CommandTester($this->command); + $commandTester->execute( + [ + GeneratePatchCommand::MODULE_NAME => 'Fake_Module', + GeneratePatchCommand::INPUT_KEY_PATCH_NAME => 'SomePatch' + ] + ); + } +} diff --git a/app/code/Magento/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php index d9b89b23d3d69..204094571ba3b 100644 --- a/app/code/Magento/Dhl/Model/Carrier.php +++ b/app/code/Magento/Dhl/Model/Carrier.php @@ -676,6 +676,7 @@ public function getDhlProducts($doc) 'H' => __('Economy select'), 'J' => __('Jumbo box'), 'M' => __('Express 10:30'), + 'N' => __('Domestic express'), 'V' => __('Europack'), 'Y' => __('Express 12:00'), ]; @@ -1767,9 +1768,8 @@ protected function _shipmentDetails($xml, $rawRequest, $originRegion = '') */ $nodeShipmentDetails->addChild('DoorTo', 'DD'); $nodeShipmentDetails->addChild('DimensionUnit', substr($this->_getDimensionUnit(), 0, 1)); - if ($package['params']['container'] == self::DHL_CONTENT_TYPE_NON_DOC) { - $packageType = 'CP'; - } + $contentType = isset($package['params']['container']) ? $package['params']['container'] : ''; + $packageType = $contentType === self::DHL_CONTENT_TYPE_NON_DOC ? 'CP' : ''; $nodeShipmentDetails->addChild('PackageType', $packageType); if ($this->isDutiable($rawRequest->getOrigCountryId(), $rawRequest->getDestCountryId())) { $nodeShipmentDetails->addChild('IsDutiable', 'Y'); diff --git a/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php b/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php index bc0321884aa0f..489157b442c8c 100644 --- a/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php +++ b/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php @@ -333,6 +333,7 @@ public function dhlProductsDataProvider(): array 'H' => 'Economy select', 'J' => 'Jumbo box', 'M' => 'Express 10:30', + 'N' => 'Domestic express', 'V' => 'Europack', 'Y' => 'Express 12:00', ], diff --git a/app/code/Magento/Dhl/etc/config.xml b/app/code/Magento/Dhl/etc/config.xml index 3408447e70650..deb162c07ba25 100644 --- a/app/code/Magento/Dhl/etc/config.xml +++ b/app/code/Magento/Dhl/etc/config.xml @@ -21,7 +21,7 @@ <active>0</active> <title>DHL 0 - 1,3,4,8,P,Q,E,F,H,J,M,V,Y + 1,3,4,8,P,Q,E,F,H,J,M,N,V,Y 2,5,6,7,9,B,C,D,U,K,L,G,W,I,N,O,R,S,T,X G https://xmlpi-ea.dhl.com/XMLShippingServlet diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Link.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Link.php index 8d5f64e02be47..1b53afc520731 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Link.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Link.php @@ -4,27 +4,36 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; +use Magento\Catalog\Controller\Adminhtml\Product\Edit as ProductEdit; use Magento\Downloadable\Helper\Download as DownloadHelper; +use Magento\Downloadable\Helper\File; +use Magento\Downloadable\Model\Link as ModelLink; use Magento\Framework\App\Response\Http as HttpResponse; -class Link extends \Magento\Catalog\Controller\Adminhtml\Product\Edit +class Link extends ProductEdit { /** - * @return \Magento\Downloadable\Model\Link + * Create link + * + * @return ModelLink */ protected function _createLink() { - return $this->_objectManager->create(\Magento\Downloadable\Model\Link::class); + return $this->_objectManager->create(ModelLink::class); } /** - * @return \Magento\Downloadable\Model\Link + * Get link + * + * @return ModelLink */ protected function _getLink() { - return $this->_objectManager->get(\Magento\Downloadable\Model\Link::class); + return $this->_objectManager->get(ModelLink::class); } /** @@ -34,10 +43,10 @@ protected function _getLink() * @param string $resourceType * @return void */ - protected function _processDownload($resource, $resourceType) + protected function _processDownload(string $resource, string $resourceType) { - /* @var $helper \Magento\Downloadable\Helper\Download */ - $helper = $this->_objectManager->get(\Magento\Downloadable\Helper\Download::class); + /* @var $helper DownloadHelper */ + $helper = $this->_objectManager->get(DownloadHelper::class); $helper->setResource($resource, $resourceType); $fileName = $helper->getFilename(); @@ -77,7 +86,7 @@ protected function _processDownload($resource, $resourceType) //Rendering $response->clearBody(); $response->sendHeaders(); - + $helper->output(); } @@ -90,7 +99,7 @@ public function execute() { $linkId = $this->getRequest()->getParam('id', 0); $type = $this->getRequest()->getParam('type', 0); - /** @var \Magento\Downloadable\Model\Link $link */ + /** @var ModelLink $link */ $link = $this->_createLink()->load($linkId); if ($link->getId()) { $resource = ''; @@ -101,7 +110,7 @@ public function execute() $resourceType = DownloadHelper::LINK_TYPE_URL; } elseif ($link->getLinkType() == DownloadHelper::LINK_TYPE_FILE) { $resource = $this->_objectManager->get( - \Magento\Downloadable\Helper\File::class + File::class )->getFilePath( $this->_getLink()->getBasePath(), $link->getLinkFile() @@ -114,7 +123,7 @@ public function execute() $resourceType = DownloadHelper::LINK_TYPE_URL; } elseif ($link->getSampleType() == DownloadHelper::LINK_TYPE_FILE) { $resource = $this->_objectManager->get( - \Magento\Downloadable\Helper\File::class + File::class )->getFilePath( $this->_getLink()->getBaseSamplePath(), $link->getSampleFile() @@ -125,7 +134,7 @@ public function execute() try { $this->_processDownload($resource, $resourceType); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError(__('Something went wrong while getting the requested content.')); + $this->messageManager->addErrorMessage(__('Something went wrong while getting the requested content.')); } } } diff --git a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Sample.php b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Sample.php index 2e115e1ce18d3..84bd21904ea18 100644 --- a/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Sample.php +++ b/app/code/Magento/Downloadable/Controller/Adminhtml/Downloadable/Product/Edit/Sample.php @@ -4,26 +4,33 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit; use Magento\Downloadable\Helper\Download as DownloadHelper; +use Magento\Downloadable\Model\Sample as ModelSample; -class Sample extends \Magento\Downloadable\Controller\Adminhtml\Downloadable\Product\Edit\Link +class Sample extends Link { /** - * @return \Magento\Downloadable\Model\Sample + * Create link + * + * @return ModelSample */ protected function _createLink() { - return $this->_objectManager->create(\Magento\Downloadable\Model\Sample::class); + return $this->_objectManager->create(ModelSample::class); } /** - * @return \Magento\Downloadable\Model\Sample + * Get link + * + * @return ModelSample */ protected function _getLink() { - return $this->_objectManager->get(\Magento\Downloadable\Model\Sample::class); + return $this->_objectManager->get(ModelSample::class); } /** @@ -34,7 +41,7 @@ protected function _getLink() public function execute() { $sampleId = $this->getRequest()->getParam('id', 0); - /** @var \Magento\Downloadable\Model\Sample $sample */ + /** @var ModelSample $sample */ $sample = $this->_createLink()->load($sampleId); if ($sample->getId()) { $resource = ''; @@ -54,7 +61,7 @@ public function execute() try { $this->_processDownload($resource, $resourceType); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError(__('Something went wrong while getting the requested content.')); + $this->messageManager->addErrorMessage(__('Something went wrong while getting the requested content.')); } } } diff --git a/app/code/Magento/Downloadable/Controller/Download/Link.php b/app/code/Magento/Downloadable/Controller/Download/Link.php index 4766f1699afb6..2b131806fa022 100644 --- a/app/code/Magento/Downloadable/Controller/Download/Link.php +++ b/app/code/Magento/Downloadable/Controller/Download/Link.php @@ -125,7 +125,7 @@ public function execute() // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage exit(0); } catch (\Exception $e) { - $this->messageManager->addError(__('Something went wrong while getting the requested content.')); + $this->messageManager->addErrorMessage(__('Something went wrong while getting the requested content.')); } } elseif ($status == PurchasedLink::LINK_STATUS_EXPIRED) { $this->messageManager->addNotice(__('The link has expired.')); @@ -133,7 +133,7 @@ public function execute() ) { $this->messageManager->addNotice(__('The link is not available.')); } else { - $this->messageManager->addError(__('Something went wrong while getting the requested content.')); + $this->messageManager->addErrorMessage(__('Something went wrong while getting the requested content.')); } return $this->_redirect('*/customer/products'); } diff --git a/app/code/Magento/Downloadable/Controller/Download/LinkSample.php b/app/code/Magento/Downloadable/Controller/Download/LinkSample.php index c449f8f54872f..1be97435fff84 100644 --- a/app/code/Magento/Downloadable/Controller/Download/LinkSample.php +++ b/app/code/Magento/Downloadable/Controller/Download/LinkSample.php @@ -69,7 +69,7 @@ public function execute() // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage exit(0); } catch (\Exception $e) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('Sorry, there was an error getting requested content. Please contact the store owner.') ); } diff --git a/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php b/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php index 971feafb857a9..2a07a3a49639f 100644 --- a/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php +++ b/app/code/Magento/Downloadable/Observer/SetLinkStatusObserver.php @@ -61,6 +61,7 @@ public function execute(\Magento\Framework\Event\Observer $observer) 'payment_pending' => \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_PENDING_PAYMENT, 'payment_review' => \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_PAYMENT_REVIEW, ]; + $expiredOrderItemIds = []; $downloadableItemsStatuses = []; $orderItemStatusToEnable = $this->_scopeConfig->getValue( @@ -114,6 +115,10 @@ public function execute(\Magento\Framework\Event\Observer $observer) if (in_array($item->getStatusId(), $availableStatuses)) { $downloadableItemsStatuses[$item->getId()] = $linkStatuses['avail']; } + + if ($item->getQtyOrdered() - $item->getQtyRefunded() == 0) { + $expiredOrderItemIds[] = $item->getId(); + } } } } @@ -141,10 +146,22 @@ public function execute(\Magento\Framework\Event\Observer $observer) } } + if ($expiredOrderItemIds) { + $linkPurchased = $this->_createItemsCollection()->addFieldToFilter( + 'order_item_id', + ['in' => $expiredOrderItemIds] + ); + foreach ($linkPurchased as $link) { + $link->setStatus(\Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_EXPIRED)->save(); + } + } + return $this; } /** + * Returns purchased item collection + * * @return \Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\Collection */ protected function _createItemsCollection() diff --git a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup.xml new file mode 100644 index 0000000000000..ae288c7033e17 --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup.xml @@ -0,0 +1,26 @@ + + + + + + + Goes to the Storefront Customer Dashboard page. Clicks on 'My Downloadable Products'. Validates that the provided Downloadable Product is present and Downloadable link not exist. + + + + + + + + + + + + + diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml index d45a774077ba0..5d340e6c91060 100644 --- a/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml +++ b/app/code/Magento/Downloadable/Test/Mftf/Section/StorefrontCustomerDownloadableProductsSection.xml @@ -10,5 +10,6 @@ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml new file mode 100644 index 0000000000000..d82cc25b0eccf --- /dev/null +++ b/app/code/Magento/Downloadable/Test/Mftf/Test/StorefrontAccountDownloadableProductLinkAfterPartialRefundTest.xml @@ -0,0 +1,109 @@ + + + + + + + + + <description value="Verify that Downloadable product is not available in My Download Products tab after it has been partially refunded."/> + <severity value="CRITICAL"/> + <testCaseId value="MC-35198"/> + <group value="Downloadable"/> + </annotations> + + <before> + <magentoCLI stepKey="addDownloadableDomain" command="downloadable:domains:add example.com static.magento.com"/> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + + <createData entity="ApiCategory" stepKey="createCategory"/> + <createData entity="ApiSimpleProduct" stepKey="createSimpleProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="ApiDownloadableProduct" stepKey="createDownloadableProduct"/> + <createData entity="downloadableLink1" stepKey="addDownloadableLink1"> + <requiredEntity createDataKey="createDownloadableProduct"/> + </createData> + + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + + <createData entity="Simple_US_Customer_Multiple_Addresses" stepKey="createCustomer"/> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="signIn"> + <argument name="Customer" value="$$createCustomer$$"/> + </actionGroup> + </before> + + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/> + <actionGroup ref="StorefrontCustomerLogoutActionGroup" stepKey="customerLogout"/> + + <deleteData createDataKey="createCustomer" stepKey="deleteCustomer"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createSimpleProduct" stepKey="deleteSimpleProduct"/> + <deleteData createDataKey="createDownloadableProduct" stepKey="deleteDownloadableProduct"/> + + <magentoCLI stepKey="removeDownloadableDomain" command="downloadable:domains:remove example.com static.magento.com"/> + <magentoCLI command="config:set {{EnableFlatRateConfigData.path}} {{EnableFlatRateConfigData.value}}" stepKey="enableFlatRate"/> + <magentoCLI command="indexer:reindex" stepKey="reindex"/> + <magentoCLI command="cache:flush" stepKey="flushCache"/> + </after> + + <actionGroup ref="StorefrontAddSimpleProductToShoppingCartActionGroup" stepKey="addSimpleProductToCart"> + <argument name="product" value="$$createSimpleProduct$$"/> + </actionGroup> + + <amOnPage url="{{StorefrontProductPage.url($$createDownloadableProduct.custom_attributes[url_key]$$)}}" stepKey="OpenStoreFrontProductPage"/> + <waitForPageLoad stepKey="waitForPageToLoad"/> + + <actionGroup ref="StorefrontAddToCartCustomOptionsProductPageActionGroup" stepKey="addToTheCart"> + <argument name="productName" value="$$createDownloadableProduct.name$$"/> + </actionGroup> + + <actionGroup ref="ClickViewAndEditCartFromMiniCartActionGroup" stepKey="goToShoppingCartFromMinicart"/> + <click selector="{{CheckoutCartSummarySection.proceedToCheckout}}" stepKey="clickProceedToCheckout"/> + <waitForPageLoad stepKey="waitForProceedToCheckout"/> + <waitForElementVisible selector="{{CheckoutShippingSection.shipHereButton(UK_Not_Default_Address.street[0])}}" stepKey="waitForShipHereVisible"/> + <click selector="{{CheckoutShippingSection.shipHereButton(UK_Not_Default_Address.street[0])}}" stepKey="clickShipHere"/> + <click selector="{{CheckoutShippingGuestInfoSection.next}}" stepKey="clickNext"/> + <waitForPageLoad stepKey="waitForShipmentPageLoad"/> + <checkOption selector="{{CheckoutPaymentSection.billingAddressNotSameCheckbox}}" stepKey="selectPaymentSolution"/> + <waitForElement selector="{{CheckoutPaymentSection.paymentSectionTitle}}" stepKey="waitForPaymentSectionLoaded"/> + <click selector="{{CheckoutPaymentSection.placeOrder}}" stepKey="clickPlaceOrderButton"/> + <seeElement selector="{{CheckoutSuccessMainSection.success}}" stepKey="orderIsSuccessfullyPlaced"/> + <grabTextFrom selector="{{CheckoutSuccessMainSection.orderNumber22}}" stepKey="grabOrderNumber"/> + + <actionGroup ref="AdminLoginActionGroup" stepKey="LoginAsAdmin"/> + + <amOnPage url="{{AdminOrdersPage.url}}" stepKey="onOrdersPage"/> + <actionGroup ref="SearchAdminDataGridByKeywordActionGroup" stepKey="searchOrder"> + <argument name="keyword" value="$grabOrderNumber"/> + </actionGroup> + <actionGroup ref="AdminOrderGridClickFirstRowActionGroup" stepKey="clickOrderRow"/> + + <actionGroup ref="AdminCreateInvoiceActionGroup" stepKey="createCreditMemo"/> + + <actionGroup ref="OpenOrderByIdActionGroup" stepKey="openOrder"> + <argument name="orderId" value="{$grabOrderNumber}"/> + </actionGroup> + + <actionGroup ref="AdminOpenAndFillCreditMemoRefundActionGroup" stepKey="fillCreditMemoRefund"> + <argument name="itemQtyToRefund" value="0"/> + <argument name="rowNumber" value="1"/> + </actionGroup> + + <click selector="{{AdminCreditMemoTotalSection.submitRefundOffline}}" stepKey="clickRefundOffline"/> + <waitForPageLoad stepKey="waitForResultPage"/> + + <actionGroup ref="StorefrontNotAssertDownloadableProductLinkInCustomerAccountActionGroup" stepKey="dontSeeStorefrontMyAccountDownloadableProductsLink"> + <argument name="product" value="$$createDownloadableProduct$$"/> + </actionGroup> + + </test> +</tests> diff --git a/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/Product/Edit/SampleTest.php b/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/Product/Edit/SampleTest.php index 193b001f305b2..31cba7b601eec 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/Product/Edit/SampleTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Controller/Adminhtml/Downloadable/Product/Edit/SampleTest.php @@ -184,6 +184,8 @@ public function testExecuteUrl() ->willReturn('1'); $this->sampleModel->expects($this->any())->method('getSampleType') ->willReturn('url'); + $this->sampleModel->expects($this->once())->method('getSampleUrl') + ->willReturn('http://example.com/simple.jpg'); $this->objectManager->expects($this->once())->method('create') ->willReturn($this->sampleModel); diff --git a/app/code/Magento/Downloadable/Test/Unit/Controller/Download/LinkTest.php b/app/code/Magento/Downloadable/Test/Unit/Controller/Download/LinkTest.php index b7483f3658d69..f9e464a3948f1 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Controller/Download/LinkTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Controller/Download/LinkTest.php @@ -327,7 +327,7 @@ public function testExceptionInUpdateLinkStatus($mimeType, $disposition) $this->linkPurchasedItem->expects($this->any())->method('setStatus')->with('expired')->willReturnSelf(); $this->linkPurchasedItem->expects($this->any())->method('save')->willThrowException(new \Exception()); $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('Something went wrong while getting the requested content.') ->willReturnSelf(); $this->redirect->expects($this->once())->method('redirect')->with($this->response, '*/customer/products', []); @@ -494,7 +494,7 @@ public function linkNotAvailableDataProvider() ['addNotice', 'expired', 'The link has expired.'], ['addNotice', 'pending', 'The link is not available.'], ['addNotice', 'payment_review', 'The link is not available.'], - ['addError', 'wrong_status', 'Something went wrong while getting the requested content.'] + ['addErrorMessage', 'wrong_status', 'Something went wrong while getting the requested content.'] ]; } diff --git a/app/code/Magento/Downloadable/Test/Unit/Observer/SetLinkStatusObserverTest.php b/app/code/Magento/Downloadable/Test/Unit/Observer/SetLinkStatusObserverTest.php index 46a3ef6717582..b5be0309bb5be 100644 --- a/app/code/Magento/Downloadable/Test/Unit/Observer/SetLinkStatusObserverTest.php +++ b/app/code/Magento/Downloadable/Test/Unit/Observer/SetLinkStatusObserverTest.php @@ -189,7 +189,7 @@ public function testSetLinkStatusPending($orderState, array $orderStateMapping) ] ); - $this->itemsFactory->expects($this->once()) + $this->itemsFactory->expects($this->any()) ->method('create') ->willReturn( $this->createLinkItemCollection( @@ -243,7 +243,7 @@ public function testSetLinkStatusClosed() ] ); - $this->itemsFactory->expects($this->once()) + $this->itemsFactory->expects($this->any()) ->method('create') ->willReturn( $this->createLinkItemCollection( @@ -308,7 +308,7 @@ public function testSetLinkStatusInvoiced() ] ); - $this->itemsFactory->expects($this->once()) + $this->itemsFactory->expects($this->any()) ->method('create') ->willReturn( $this->createLinkItemCollection( @@ -344,6 +344,137 @@ public function testSetLinkStatusEmptyOrder() $this->assertInstanceOf(SetLinkStatusObserver::class, $result); } + public function testSetLinkStatusExpired() + { + $this->scopeConfig->expects($this->once()) + ->method('getValue') + ->with( + \Magento\Downloadable\Model\Link\Purchased\Item::XML_PATH_ORDER_ITEM_STATUS, + ScopeInterface::SCOPE_STORE, + 1 + ) + ->willReturn(Item::STATUS_PENDING); + + $this->observerMock->expects($this->once()) + ->method('getEvent') + ->willReturn($this->eventMock); + + $this->eventMock->expects($this->once()) + ->method('getOrder') + ->willReturn($this->orderMock); + + $this->orderMock->expects($this->once()) + ->method('getId') + ->willReturn(1); + + $this->orderMock->expects($this->once()) + ->method('getStoreId') + ->willReturn(1); + + $this->orderMock->expects($this->atLeastOnce()) + ->method('getState') + ->willReturn(Order::STATE_PROCESSING); + + $this->orderMock->expects($this->any()) + ->method('getAllItems') + ->willReturn( + [ + $this->createRefundOrderItem(2, 2, 2), + $this->createRefundOrderItem(3, 2, 1), + $this->createRefundOrderItem(4, 3, 3), + ] + ); + + $this->itemsFactory->expects($this->any()) + ->method('create') + ->willReturn( + $this->createLinkItemToExpireCollection( + [2, 4], + [ + $this->createLinkItem( + 'available', + 2, + true, + \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_EXPIRED + ), + $this->createLinkItem( + 'pending_payment', + 4, + true, + \Magento\Downloadable\Model\Link\Purchased\Item::LINK_STATUS_EXPIRED + ), + ] + ) + ); + + $result = $this->setLinkStatusObserver->execute($this->observerMock); + $this->assertInstanceOf(SetLinkStatusObserver::class, $result); + } + + /** + * @param $id + * @param int $qtyOrdered + * @param int $qtyRefunded + * @param string $productType + * @param string $realProductType + * @return \Magento\Sales\Model\Order\Item|MockObject + */ + private function createRefundOrderItem( + $id, + $qtyOrdered, + $qtyRefunded, + $productType = DownloadableProductType::TYPE_DOWNLOADABLE, + $realProductType = DownloadableProductType::TYPE_DOWNLOADABLE + ) { + $item = $this->getMockBuilder(Item::class) + ->disableOriginalConstructor() + ->setMethods([ + 'getId', + 'getQtyOrdered', + 'getQtyRefunded', + 'getProductType', + 'getRealProductType' + ])->getMock(); + $item->expects($this->any()) + ->method('getId') + ->willReturn($id); + $item->expects($this->any()) + ->method('getQtyOrdered') + ->willReturn($qtyOrdered); + $item->expects($this->any()) + ->method('getQtyRefunded') + ->willReturn($qtyRefunded); + $item->expects($this->any()) + ->method('getProductType') + ->willReturn($productType); + $item->expects($this->any()) + ->method('getRealProductType') + ->willReturn($realProductType); + + return $item; + } + + /** + * @param array $expectedOrderItemIds + * @param array $items + * @return LinkItemCollection|MockObject + */ + private function createLinkItemToExpireCollection(array $expectedOrderItemIds, array $items) + { + $linkItemCollection = $this->getMockBuilder( + \Magento\Downloadable\Model\ResourceModel\Link\Purchased\Item\Collection::class + ) + ->disableOriginalConstructor() + ->setMethods(['addFieldToFilter']) + ->getMock(); + $linkItemCollection->expects($this->any()) + ->method('addFieldToFilter') + ->with('order_item_id', ['in' => $expectedOrderItemIds]) + ->willReturn($items); + + return $linkItemCollection; + } + /** * @param $id * @param int $statusId @@ -359,7 +490,7 @@ private function createOrderItem( ) { $item = $this->getMockBuilder(Item::class) ->disableOriginalConstructor() - ->setMethods(['getId', 'getProductType', 'getRealProductType', 'getStatusId']) + ->setMethods(['getId', 'getProductType', 'getRealProductType', 'getStatusId', 'getQtyOrdered']) ->getMock(); $item->expects($this->any()) ->method('getId') @@ -373,6 +504,9 @@ private function createOrderItem( $item->expects($this->any()) ->method('getStatusId') ->willReturn($statusId); + $item->expects($this->any()) + ->method('getQtyOrdered') + ->willReturn(1); return $item; } @@ -390,7 +524,7 @@ private function createLinkItemCollection(array $expectedOrderItemIds, array $it ->disableOriginalConstructor() ->setMethods(['addFieldToFilter']) ->getMock(); - $linkItemCollection->expects($this->once()) + $linkItemCollection->expects($this->any()) ->method('addFieldToFilter') ->with('order_item_id', ['in' => $expectedOrderItemIds]) ->willReturn($items); @@ -415,11 +549,11 @@ private function createLinkItem($status, $orderItemId, $isSaved = false, $expect ->method('getStatus') ->willReturn($status); if ($isSaved) { - $linkItem->expects($this->once()) + $linkItem->expects($this->any()) ->method('setStatus') ->with($expectedStatus) ->willReturnSelf(); - $linkItem->expects($this->once()) + $linkItem->expects($this->any()) ->method('save') ->willReturnSelf(); } diff --git a/app/code/Magento/DownloadableGraphQl/Model/Wishlist/ItemLinks.php b/app/code/Magento/DownloadableGraphQl/Model/Wishlist/ItemLinks.php new file mode 100644 index 0000000000000..68223054aa806 --- /dev/null +++ b/app/code/Magento/DownloadableGraphQl/Model/Wishlist/ItemLinks.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\DownloadableGraphQl\Model\Wishlist; + +use Magento\Catalog\Model\Product\Configuration\Item\ItemInterface; +use Magento\Downloadable\Helper\Catalog\Product\Configuration; +use Magento\DownloadableGraphQl\Model\ConvertLinksToArray; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Fetches the selected item downloadable links + */ +class ItemLinks implements ResolverInterface +{ + /** + * @var ConvertLinksToArray + */ + private $convertLinksToArray; + + /** + * @var Configuration + */ + private $downloadableConfiguration; + + /** + * @param ConvertLinksToArray $convertLinksToArray + * @param Configuration $downloadableConfiguration + */ + public function __construct( + ConvertLinksToArray $convertLinksToArray, + Configuration $downloadableConfiguration + ) { + $this->convertLinksToArray = $convertLinksToArray; + $this->downloadableConfiguration = $downloadableConfiguration; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$value['itemModel'] instanceof ItemInterface) { + throw new LocalizedException(__('"itemModel" should be a "%instance" instance', [ + 'instance' => ItemInterface::class + ])); + } + /** @var ItemInterface $wishlistItem */ + $itemItem = $value['itemModel']; + + $links = $this->downloadableConfiguration->getLinks($itemItem); + $links = $this->convertLinksToArray->execute($links); + + return $links; + } +} diff --git a/app/code/Magento/DownloadableGraphQl/etc/graphql/di.xml b/app/code/Magento/DownloadableGraphQl/etc/graphql/di.xml index c95667de15ac3..51a630d59ca0f 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/graphql/di.xml +++ b/app/code/Magento/DownloadableGraphQl/etc/graphql/di.xml @@ -39,4 +39,11 @@ </argument> </arguments> </type> + <type name="Magento\WishlistGraphQl\Model\Resolver\Type\WishlistItemType"> + <arguments> + <argument name="supportedTypes" xsi:type="array"> + <item name="downloadable" xsi:type="string">DownloadableWishlistItem</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls index 5863e62e81b1b..ba178bb1a427e 100644 --- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls +++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls @@ -64,3 +64,8 @@ type DownloadableProductSamples @doc(description: "DownloadableProductSamples de sample_type: DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample") sample_file: String @deprecated(reason: "`sample_url` serves to get the downloadable sample") } + +type DownloadableWishlistItem implements WishlistItemInterface @doc(description: "A downloadable product wish list item") { + links_v2: [DownloadableProductLinks] @doc(description: "An array containing information about the selected links") @resolver(class: "\\Magento\\DownloadableGraphQl\\Model\\Wishlist\\ItemLinks") + samples: [DownloadableProductSamples] @doc(description: "An array containing information about the selected samples") @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\Product\\Samples") +} diff --git a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php index 0e1e4f035fc14..b29d45f75c993 100644 --- a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php +++ b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php @@ -679,6 +679,9 @@ public function joinAttribute($alias, $attribute, $bind, $filter = null, $joinTy throw new LocalizedException(__('The foreign key is invalid. Verify the foreign key and try again.')); } + $entity = null; + $attrArr = []; + // try to explode combined entity/attribute if supplied if (is_string($attribute)) { $attrArr = explode('/', $attribute); @@ -1121,12 +1124,13 @@ public function _loadEntities($printQuery = false, $logQuery = false) $this->printLogQuery($printQuery, $logQuery); + /** + * Prepare select query + * @var string|\Magento\Framework\DB\Select $query + */ + $query = $this->getSelect(); + try { - /** - * Prepare select query - * @var string $query - */ - $query = $this->getSelect(); $rows = $this->_fetchAll($query); } catch (\Exception $e) { $this->printLogQuery(false, true, $query); @@ -1192,12 +1196,12 @@ public function _loadAttributes($printQuery = false, $logQuery = false) $selectGroups = $this->_resourceHelper->getLoadAttributesSelectGroups($selects); foreach ($selectGroups as $selects) { if (!empty($selects)) { + if (is_array($selects)) { + $select = implode(' UNION ALL ', $selects); + } else { + $select = $selects; + } try { - if (is_array($selects)) { - $select = implode(' UNION ALL ', $selects); - } else { - $select = $selects; - } $values = $this->getConnection()->fetchAll($select); } catch (\Exception $e) { $this->printLogQuery(true, true, $select); @@ -1238,10 +1242,12 @@ protected function _getLoadAttributesSelect($table, $attributeIds = []) ['t_d.attribute_id'] )->where( " e.entity_id IN (?)", - array_keys($this->_itemsById) + array_keys($this->_itemsById), + \Zend_Db::INT_TYPE )->where( 't_d.attribute_id IN (?)', - $attributeIds + $attributeIds, + \Zend_Db::INT_TYPE ); if ($entity->getEntityTable() == \Magento\Eav\Model\Entity::DEFAULT_ENTITY_TABLE && $entity->getTypeId()) { diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php index 637d4e17e852d..29cad62bf0ca4 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute.php @@ -778,7 +778,8 @@ public function getValidAttributeIds($attributeIds) ['attribute_id'] )->where( 'attribute_id IN (?)', - $attributeIds + $attributeIds, + \Zend_Db::INT_TYPE ); return $connection->fetchCol($select); diff --git a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php index 6fce6bd2dc44e..bcd8f2bb04e69 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php +++ b/app/code/Magento/Eav/Model/ResourceModel/Entity/Attribute/Collection.php @@ -6,7 +6,17 @@ namespace Magento\Eav\Model\ResourceModel\Entity\Attribute; +use Magento\Eav\Model\Config; use Magento\Eav\Model\Entity\Type; +use Magento\Eav\Model\ResourceModel\Entity\Attribute; +use Magento\Framework\Data\Collection\Db\FetchStrategyInterface; +use Magento\Framework\Data\Collection\EntityFactoryInterface; +use Magento\Framework\DB\Adapter\AdapterInterface; +use Magento\Framework\DB\Select; +use Magento\Framework\Event\ManagerInterface; +use Magento\Framework\Model\ResourceModel\Db\AbstractDb; +use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection; +use Psr\Log\LoggerInterface; /** * EAV attribute resource collection @@ -14,8 +24,9 @@ * @api * @author Magento Core Team <core@magentocommerce.com> * @since 100.0.2 + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection +class Collection extends AbstractCollection { /** * Add attribute set info flag @@ -25,28 +36,28 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab protected $_addSetInfoFlag = false; /** - * @var \Magento\Eav\Model\Config + * @var Config */ protected $eavConfig; /** - * @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory - * @param \Psr\Log\LoggerInterface $logger - * @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy - * @param \Magento\Framework\Event\ManagerInterface $eventManager - * @param \Magento\Eav\Model\Config $eavConfig - * @param \Magento\Framework\DB\Adapter\AdapterInterface $connection - * @param \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource + * @param EntityFactoryInterface $entityFactory + * @param LoggerInterface $logger + * @param FetchStrategyInterface $fetchStrategy + * @param ManagerInterface $eventManager + * @param Config $eavConfig + * @param AdapterInterface $connection + * @param AbstractDb $resource * @codeCoverageIgnore */ public function __construct( - \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory, - \Psr\Log\LoggerInterface $logger, - \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy, - \Magento\Framework\Event\ManagerInterface $eventManager, - \Magento\Eav\Model\Config $eavConfig, - \Magento\Framework\DB\Adapter\AdapterInterface $connection = null, - \Magento\Framework\Model\ResourceModel\Db\AbstractDb $resource = null + EntityFactoryInterface $entityFactory, + LoggerInterface $logger, + FetchStrategyInterface $fetchStrategy, + ManagerInterface $eventManager, + Config $eavConfig, + AdapterInterface $connection = null, + AbstractDb $resource = null ) { $this->eavConfig = $eavConfig; parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $connection, $resource); @@ -62,7 +73,7 @@ protected function _construct() { $this->_init( \Magento\Eav\Model\Entity\Attribute::class, - \Magento\Eav\Model\ResourceModel\Entity\Attribute::class + Attribute::class ); } @@ -94,7 +105,7 @@ protected function _getLoadDataFields() */ public function useLoadDataFields() { - $this->getSelect()->reset(\Magento\Framework\DB\Select::COLUMNS); + $this->getSelect()->reset(Select::COLUMNS); $this->getSelect()->columns($this->_getLoadDataFields()); return $this; @@ -221,7 +232,8 @@ public function setInAllAttributeSetsFilter(array $setIds) ) ->where( 'entity_attribute.attribute_set_id IN (?)', - $setIds + $setIds, + \Zend_Db::INT_TYPE ) ->group('entity_attribute.attribute_id') ->having(new \Zend_Db_Expr('COUNT(*)') . ' = ' . count($setIds)); @@ -394,7 +406,8 @@ protected function _addSetInfo() ['group_sort_order' => 'sort_order'] )->where( 'attribute_id IN (?)', - $attributeIds + $attributeIds, + \Zend_Db::INT_TYPE ); $result = $connection->fetchAll($select); @@ -481,7 +494,7 @@ public function addStoreLabel($storeId) public function getSelectCountSql() { $countSelect = parent::getSelectCountSql(); - $countSelect->reset(\Magento\Framework\DB\Select::COLUMNS); + $countSelect->reset(Select::COLUMNS); $countSelect->columns('COUNT(DISTINCT main_table.attribute_id)'); return $countSelect; } diff --git a/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php b/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php index 1971eeeff3147..e8c8d4c5190fe 100644 --- a/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php +++ b/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php @@ -152,7 +152,7 @@ public function execute($entityType, $entityData, $arguments = []) ['value' => 't.value', 'attribute_id' => 't.attribute_id'] ) ->where($metadata->getLinkField() . ' = ?', $entityData[$metadata->getLinkField()]) - ->where('attribute_id IN (?)', $attributeIds); + ->where('attribute_id IN (?)', $attributeIds, \Zend_Db::INT_TYPE); $attributeIdentifiers = []; foreach ($context as $scope) { //TODO: if (in table exists context field) diff --git a/app/code/Magento/GraphQl/etc/di.xml b/app/code/Magento/GraphQl/etc/di.xml index fca6c425e2507..d6168cdc37600 100644 --- a/app/code/Magento/GraphQl/etc/di.xml +++ b/app/code/Magento/GraphQl/etc/di.xml @@ -29,6 +29,7 @@ <arguments> <argument name="factoryMapByConfigElementType" xsi:type="array"> <item name="graphql_interface" xsi:type="object">Magento\Framework\GraphQl\Config\Element\InterfaceFactory</item> + <item name="graphql_union" xsi:type="object">Magento\Framework\GraphQl\Config\Element\UnionFactory</item> <item name="graphql_type" xsi:type="object">Magento\Framework\GraphQl\Config\Element\TypeFactory</item> <item name="graphql_input" xsi:type="object">Magento\Framework\GraphQl\Config\Element\InputFactory</item> <item name="graphql_enum" xsi:type="object">Magento\Framework\GraphQl\Config\Element\EnumFactory</item> @@ -64,6 +65,7 @@ <item name="Magento\Framework\GraphQl\Config\Element\Type" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Output\OutputTypeObject</item> <item name="Magento\Framework\GraphQl\Config\Element\Input" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Input\InputObjectType</item> <item name="Magento\Framework\GraphQl\Config\Element\InterfaceType" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Output\OutputInterfaceObject</item> + <item name="Magento\Framework\GraphQl\Config\Element\UnionType" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Output\OutputUnionObject</item> <item name="Magento\Framework\GraphQl\Config\Element\Enum" xsi:type="string">Magento\Framework\GraphQl\Schema\Type\Enum\Enum</item> </argument> </arguments> @@ -78,6 +80,7 @@ <argument name="formatters" xsi:type="array"> <item name="fields" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter\Fields</item> <item name="interfaces" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter\Interfaces</item> + <item name="unions" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter\Unions</item> <item name="resolveType" xsi:type="object">Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter\ResolveType</item> </argument> </arguments> @@ -85,6 +88,7 @@ <type name="Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeReaderComposite"> <arguments> <argument name="typeReaders" xsi:type="array"> + <item name="union_type" xsi:type="object">Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\UnionType</item> <item name="enum_type" xsi:type="object">Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\EnumType</item> <item name="object_type" xsi:type="object">Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\ObjectType</item> <item name="input_object_type" xsi:type="object">Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\InputObjectType</item> diff --git a/app/code/Magento/GraphQl/etc/schema.graphqls b/app/code/Magento/GraphQl/etc/schema.graphqls index 2595ad09c072a..7366567c2b95d 100644 --- a/app/code/Magento/GraphQl/etc/schema.graphqls +++ b/app/code/Magento/GraphQl/etc/schema.graphqls @@ -30,14 +30,14 @@ directive @resolver(class: String="") on QUERY | OBJECT | FIELD_DEFINITION | ARGUMENT_DEFINITION - | INTERFACE - | UNION | ENUM | ENUM_VALUE | INPUT_OBJECT | INPUT_FIELD_DEFINITION -directive @typeResolver(class: String="") on INTERFACE | OBJECT +directive @typeResolver(class: String="") on UNION + | INTERFACE + | OBJECT directive @cache(cacheIdentity: String="" cacheable: Boolean=true) on QUERY diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php index dff6560ebf768..43cc467ad390b 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php @@ -99,10 +99,10 @@ public function execute() ); } catch (\Exception $e) { $this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e); - $this->messageManager->addError(__('Please correct the data sent value.')); + $this->messageManager->addErrorMessage(__('Please correct the data sent value.')); } } else { - $this->messageManager->addError(__('Please correct the data sent value.')); + $this->messageManager->addErrorMessage(__('Please correct the data sent value.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/GetFilter.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/GetFilter.php index 722d32c9eb21a..789df5dbc466f 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/GetFilter.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/GetFilter.php @@ -35,10 +35,10 @@ public function execute() ); return $resultLayout; } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } else { - $this->messageManager->addError(__('Please correct the data sent value.')); + $this->messageManager->addErrorMessage(__('Please correct the data sent value.')); } /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php index 7c119e1dd683d..9918ef8908956 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php @@ -3,62 +3,71 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ImportExport\Controller\Adminhtml\Import; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\App\ObjectManager; +use Magento\Framework\App\Response\Http\FileFactory; use Magento\Framework\Component\ComponentRegistrar; +use Magento\Framework\Controller\Result\Raw; +use Magento\Framework\Controller\Result\RawFactory; use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Filesystem\Directory\ReadFactory; use Magento\ImportExport\Controller\Adminhtml\Import as ImportController; +use Magento\ImportExport\Model\Import\SampleFileProvider; /** * Download sample file controller */ -class Download extends ImportController +class Download extends ImportController implements HttpGetActionInterface { const SAMPLE_FILES_MODULE = 'Magento_ImportExport'; /** - * @var \Magento\Framework\Controller\Result\RawFactory + * @var RawFactory */ protected $resultRawFactory; /** - * @var \Magento\Framework\Filesystem\Directory\ReadFactory + * @var ReadFactory */ protected $readFactory; /** - * @var \Magento\Framework\Component\ComponentRegistrar + * @var ComponentRegistrar */ protected $componentRegistrar; /** - * @var \Magento\Framework\App\Response\Http\FileFactory + * @var FileFactory */ protected $fileFactory; /** - * @var \Magento\ImportExport\Model\Import\SampleFileProvider + * @var SampleFileProvider */ private $sampleFileProvider; /** - * @param \Magento\Backend\App\Action\Context $context - * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory - * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory - * @param \Magento\Framework\Filesystem\Directory\ReadFactory $readFactory - * @param \Magento\ImportExport\Model\Import\SampleFileProvider $sampleFileProvider + * @param Context $context + * @param FileFactory $fileFactory + * @param RawFactory $resultRawFactory + * @param ReadFactory $readFactory * @param ComponentRegistrar $componentRegistrar - * @param \Magento\ImportExport\Model\Import\SampleFileProvider|null $sampleFileProvider + * @param SampleFileProvider|null $sampleFileProvider */ public function __construct( - \Magento\Backend\App\Action\Context $context, - \Magento\Framework\App\Response\Http\FileFactory $fileFactory, - \Magento\Framework\Controller\Result\RawFactory $resultRawFactory, - \Magento\Framework\Filesystem\Directory\ReadFactory $readFactory, - \Magento\Framework\Component\ComponentRegistrar $componentRegistrar, - \Magento\ImportExport\Model\Import\SampleFileProvider $sampleFileProvider = null + Context $context, + FileFactory $fileFactory, + RawFactory $resultRawFactory, + ReadFactory $readFactory, + ComponentRegistrar $componentRegistrar, + SampleFileProvider $sampleFileProvider = null ) { parent::__construct( $context @@ -68,14 +77,14 @@ public function __construct( $this->readFactory = $readFactory; $this->componentRegistrar = $componentRegistrar; $this->sampleFileProvider = $sampleFileProvider - ?: \Magento\Framework\App\ObjectManager::getInstance() - ->get(\Magento\ImportExport\Model\Import\SampleFileProvider::class); + ?: ObjectManager::getInstance() + ->get(SampleFileProvider::class); } /** * Download sample file action * - * @return \Magento\Framework\Controller\Result\Raw + * @return Raw */ public function execute() { @@ -89,7 +98,7 @@ public function execute() try { $fileContents = $this->sampleFileProvider->getFileContents($entityName); } catch (NoSuchEntityException $e) { - $this->messageManager->addError(__('There is no sample file for this entity.')); + $this->messageManager->addErrorMessage(__('There is no sample file for this entity.')); return $this->getResultRedirect(); } @@ -105,13 +114,15 @@ public function execute() $fileSize ); - /** @var \Magento\Framework\Controller\Result\Raw $resultRaw */ $resultRaw = $this->resultRawFactory->create(); $resultRaw->setContents($fileContents); + return $resultRaw; } /** + * Get redirect result + * * @return Redirect */ private function getResultRedirect(): Redirect diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php index c18e666260898..4be73fe384ae0 100644 --- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php +++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php @@ -3,15 +3,18 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\ImportExport\Controller\Adminhtml\Import; +use Magento\Backend\Model\View\Result\Redirect; use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface; +use Magento\Framework\Controller\ResultFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\Framework\View\Result\Layout; +use Magento\ImportExport\Block\Adminhtml\Import\Frame\Result; use Magento\ImportExport\Controller\Adminhtml\ImportResult as ImportResultController; use Magento\ImportExport\Model\Import; -use Magento\ImportExport\Block\Adminhtml\Import\Frame\Result; -use Magento\Framework\Controller\ResultFactory; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\ImportExport\Model\Import\Adapter as ImportAdapter; /** * Import validate controller action. @@ -27,16 +30,16 @@ class Validate extends ImportResultController implements HttpPostActionInterface /** * Validate uploaded files action * - * @return \Magento\Framework\Controller\ResultInterface - * @SuppressWarnings(PHPMD.Superglobals) + * @return ResultInterface */ public function execute() { $data = $this->getRequest()->getPostValue(); - /** @var \Magento\Framework\View\Result\Layout $resultLayout */ + /** @var Layout $resultLayout */ $resultLayout = $this->resultFactory->create(ResultFactory::TYPE_LAYOUT); /** @var $resultBlock Result */ $resultBlock = $resultLayout->getLayout()->getBlock('import.frame.result'); + //phpcs:disable Magento2.Security.Superglobal if ($data) { // common actions $resultBlock->addAction( @@ -44,7 +47,6 @@ public function execute() 'import_validation_container' ); - /** @var $import \Magento\ImportExport\Model\Import */ $import = $this->getImport()->setData($data); try { $source = $import->uploadFileAndGetSource(); @@ -59,8 +61,8 @@ public function execute() $resultBlock->addError(__('The file was not uploaded.')); return $resultLayout; } - $this->messageManager->addError(__('Sorry, but the data is invalid or the file is not uploaded.')); - /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */ + $this->messageManager->addErrorMessage(__('Sorry, but the data is invalid or the file is not uploaded.')); + /** @var Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); $resultRedirect->setPath('adminhtml/*/index'); return $resultRedirect; @@ -100,7 +102,7 @@ private function processValidationResult($validationResult, $resultBlock) $errorAggregator->getErrorsCount() ) ); - + $this->addErrorMessages($resultBlock, $errorAggregator); } else { if ($errorAggregator->getErrorsCount()) { diff --git a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Import/ValidateTest.php b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Import/ValidateTest.php index 06c89a3e9e543..e54b1e470b54d 100644 --- a/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Import/ValidateTest.php +++ b/app/code/Magento/ImportExport/Test/Unit/Controller/Adminhtml/Import/ValidateTest.php @@ -164,7 +164,7 @@ public function testNoDataWasPosted() ]); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('Sorry, but the data is invalid or the file is not uploaded.')); $this->assertEquals($resultRedirectMock, $this->validate->execute()); diff --git a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassChangelog.php b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassChangelog.php index 6934284c8e65e..8909fa999528a 100644 --- a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassChangelog.php +++ b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassChangelog.php @@ -22,7 +22,7 @@ public function execute() { $indexerIds = $this->getRequest()->getParam('indexer_ids'); if (!is_array($indexerIds)) { - $this->messageManager->addError(__('Please select indexers.')); + $this->messageManager->addErrorMessage(__('Please select indexers.')); } else { try { foreach ($indexerIds as $indexerId) { @@ -36,7 +36,7 @@ public function execute() __('%1 indexer(s) are in "Update by Schedule" mode.', count($indexerIds)) ); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addException( $e, diff --git a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassInvalidate.php b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassInvalidate.php index 0cc203a547b3a..2fec3aac698b6 100644 --- a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassInvalidate.php +++ b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassInvalidate.php @@ -40,7 +40,7 @@ public function execute() { $indexerIds = $this->getRequest()->getParam('indexer_ids'); if (!is_array($indexerIds)) { - $this->messageManager->addError(__('Please select indexers.')); + $this->messageManager->addErrorMessage(__('Please select indexers.')); } else { try { foreach ($indexerIds as $indexerId) { @@ -52,7 +52,7 @@ public function execute() __('%1 indexer(s) were invalidated.', count($indexerIds)) ); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addException( $e, diff --git a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassOnTheFly.php b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassOnTheFly.php index 21fa7a61c621f..f8c3c58f5413b 100644 --- a/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassOnTheFly.php +++ b/app/code/Magento/Indexer/Controller/Adminhtml/Indexer/MassOnTheFly.php @@ -22,7 +22,7 @@ public function execute() { $indexerIds = $this->getRequest()->getParam('indexer_ids'); if (!is_array($indexerIds)) { - $this->messageManager->addError(__('Please select indexers.')); + $this->messageManager->addErrorMessage(__('Please select indexers.')); } else { try { foreach ($indexerIds as $indexerId) { @@ -36,7 +36,7 @@ public function execute() __('%1 indexer(s) are in "Update on Save" mode.', count($indexerIds)) ); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addException( $e, diff --git a/app/code/Magento/Indexer/Test/Mftf/ActionGroup/AdminOpenIndexManagementPageActionGroup.xml b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/AdminOpenIndexManagementPageActionGroup.xml new file mode 100644 index 0000000000000..79732ae4b6f3a --- /dev/null +++ b/app/code/Magento/Indexer/Test/Mftf/ActionGroup/AdminOpenIndexManagementPageActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOpenIndexManagementPageActionGroup"> + <annotations> + <description>Open index management page.</description> + </annotations> + + <amOnPage url="{{AdminIndexManagementPage.url}}" stepKey="openIndexManagementPage"/> + <waitForPageLoad stepKey="waitForPageLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassChangelogTest.php b/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassChangelogTest.php index 329e392ea1a8d..a5fc5e7bf68d1 100644 --- a/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassChangelogTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassChangelogTest.php @@ -171,7 +171,7 @@ protected function setUp(): void $this->title = $this->createMock(Title::class); $this->messageManager = $this->getMockForAbstractClass( ManagerInterface::class, - ['addError', 'addSuccess'], + ['addErrorMessage', 'addSuccess'], '', false ); @@ -206,7 +206,7 @@ public function testExecute($indexerIds, $exception, $expectsExceptionValues) if (!is_array($indexerIds)) { $this->messageManager->expects($this->once()) - ->method('addError')->with(__('Please select indexers.')) + ->method('addErrorMessage')->with(__('Please select indexers.')) ->willReturn(1); } else { $this->objectManager->expects($this->any()) @@ -235,7 +235,7 @@ public function testExecute($indexerIds, $exception, $expectsExceptionValues) if ($exception !== null) { $this->messageManager ->expects($this->exactly($expectsExceptionValues[2])) - ->method('addError') + ->method('addErrorMessage') ->with($exception->getMessage()); $this->messageManager->expects($this->exactly($expectsExceptionValues[1])) ->method('addException') diff --git a/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassInvalidateTest.php b/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassInvalidateTest.php index 9c43b8a84d1ba..a49b128681bbc 100644 --- a/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassInvalidateTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassInvalidateTest.php @@ -196,7 +196,7 @@ protected function setUp(): void $this->title = $this->createMock(Title::class); $this->messageManager = $this->getMockForAbstractClass( ManagerInterface::class, - ['addError', 'addSuccess'], + ['addErrorMessage', 'addSuccess'], '', false ); @@ -233,7 +233,7 @@ public function testExecute($indexerIds, $exception) if (!is_array($indexerIds)) { $this->messageManager->expects($this->once()) - ->method('addError')->with(__('Please select indexers.')) + ->method('addErrorMessage')->with(__('Please select indexers.')) ->willReturn(1); } else { $indexerInterface = $this->getMockForAbstractClass( @@ -261,7 +261,7 @@ public function testExecute($indexerIds, $exception) if ($exception instanceof LocalizedException) { $this->messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with($exception->getMessage()); } else { $this->messageManager->expects($this->once()) diff --git a/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassOnTheFlyTest.php b/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassOnTheFlyTest.php index 727f5965f9fe4..649db0282d12d 100644 --- a/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassOnTheFlyTest.php +++ b/app/code/Magento/Indexer/Test/Unit/Controller/Adminhtml/Indexer/MassOnTheFlyTest.php @@ -171,7 +171,7 @@ protected function setUp(): void $this->title = $this->createMock(Title::class); $this->messageManager = $this->getMockForAbstractClass( ManagerInterface::class, - ['addError', 'addSuccess'], + ['addErrorMessage', 'addSuccess'], '', false ); @@ -206,7 +206,7 @@ public function testExecute($indexerIds, $exception, $expectsExceptionValues) if (!is_array($indexerIds)) { $this->messageManager->expects($this->once()) - ->method('addError')->with(__('Please select indexers.')) + ->method('addErrorMessage')->with(__('Please select indexers.')) ->willReturn(1); } else { $this->objectManager->expects($this->any()) @@ -234,7 +234,7 @@ public function testExecute($indexerIds, $exception, $expectsExceptionValues) if ($exception !== null) { $this->messageManager->expects($this->exactly($expectsExceptionValues[2])) - ->method('addError') + ->method('addErrorMessage') ->with($exception->getMessage()); $this->messageManager->expects($this->exactly($expectsExceptionValues[1])) ->method('addException') diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Delete.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Delete.php index 4ce462bb44c89..1e4f58d0b1250 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Delete.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Delete.php @@ -27,7 +27,7 @@ public function execute() if ($integrationId) { $integrationData = $this->_integrationService->get($integrationId); if ($this->_integrationData->isConfigType($integrationData)) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __( "Uninstall the extension to remove integration '%1'.", $this->escaper->escapeHtml($integrationData[Info::DATA_NAME]) @@ -37,7 +37,7 @@ public function execute() } $integrationData = $this->_integrationService->delete($integrationId); if (!$integrationData[Info::DATA_ID]) { - $this->messageManager->addError(__('This integration no longer exists.')); + $this->messageManager->addErrorMessage(__('This integration no longer exists.')); } else { //Integration deleted successfully, now safe to delete the associated consumer data if (isset($integrationData[Info::DATA_CONSUMER_ID])) { @@ -52,10 +52,10 @@ public function execute() ); } } else { - $this->messageManager->addError(__('Integration ID is not specified or is invalid.')); + $this->messageManager->addErrorMessage(__('Integration ID is not specified or is invalid.')); } } catch (IntegrationException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->_logger->critical($e); } diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Edit.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Edit.php index 599b6017059e1..25b23065f308e 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Edit.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Edit.php @@ -27,12 +27,12 @@ public function execute() $integrationData = $this->_integrationService->get($integrationId)->getData(); $originalName = $this->escaper->escapeHtml($integrationData[Info::DATA_NAME]); } catch (IntegrationException $e) { - $this->messageManager->addError($this->escaper->escapeHtml($e->getMessage())); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($e->getMessage())); $this->_redirect('*/*/'); return; } catch (\Exception $e) { $this->_logger->critical($e); - $this->messageManager->addError(__('Internal error. Check exception log for details.')); + $this->messageManager->addErrorMessage(__('Internal error. Check exception log for details.')); $this->_redirect('*/*'); return; } @@ -41,7 +41,7 @@ public function execute() $integrationData = array_merge($integrationData, $restoredIntegration); } } else { - $this->messageManager->addError(__('Integration ID is not specified or is invalid.')); + $this->messageManager->addErrorMessage(__('Integration ID is not specified or is invalid.')); $this->_redirect('*/*/'); return; } diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/PermissionsDialog.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/PermissionsDialog.php index 8b2a94da01d70..e418fa9de1d97 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/PermissionsDialog.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/PermissionsDialog.php @@ -24,17 +24,17 @@ public function execute() $integrationData = $this->_integrationService->get($integrationId)->getData(); $this->_registry->register(self::REGISTRY_KEY_CURRENT_INTEGRATION, $integrationData); } catch (IntegrationException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_redirect('*/*/'); return; } catch (\Exception $e) { $this->_logger->critical($e); - $this->messageManager->addError(__('Internal error. Check exception log for details.')); + $this->messageManager->addErrorMessage(__('Internal error. Check exception log for details.')); $this->_redirect('*/*'); return; } } else { - $this->messageManager->addError(__('Integration ID is not specified or is invalid.')); + $this->messageManager->addErrorMessage(__('Integration ID is not specified or is invalid.')); $this->_redirect('*/*/'); return; } diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Save.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Save.php index ea255487b9df1..ac237750e7152 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/Save.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/Save.php @@ -69,19 +69,19 @@ public function execute() ); $this->_redirect('*'); } catch (\Magento\Framework\Exception\AuthenticationException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_getSession()->setIntegrationData($this->getRequest()->getPostValue()); $this->_redirectOnSaveError(); } catch (IntegrationException $e) { - $this->messageManager->addError($this->escaper->escapeHtml($e->getMessage())); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($e->getMessage())); $this->_getSession()->setIntegrationData($this->getRequest()->getPostValue()); $this->_redirectOnSaveError(); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($this->escaper->escapeHtml($e->getMessage())); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($e->getMessage())); $this->_redirectOnSaveError(); } catch (\Exception $e) { $this->_logger->critical($e); - $this->messageManager->addError($this->escaper->escapeHtml($e->getMessage())); + $this->messageManager->addErrorMessage($this->escaper->escapeHtml($e->getMessage())); $this->_redirectOnSaveError(); } } diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensDialog.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensDialog.php index 4c99dafb1d997..f4ebad4954946 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensDialog.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensDialog.php @@ -51,12 +51,12 @@ public function execute() $this->_integrationService->get($integrationId)->getData() ); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_redirect('*/*'); return; } catch (\Exception $e) { $this->_logger->critical($e); - $this->messageManager->addError(__('Internal error. Check exception log for details.')); + $this->messageManager->addErrorMessage(__('Internal error. Check exception log for details.')); $this->_redirect('*/*'); return; } diff --git a/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensExchange.php b/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensExchange.php index a49561dd95ade..2f1884b8db735 100644 --- a/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensExchange.php +++ b/app/code/Magento/Integration/Controller/Adminhtml/Integration/TokensExchange.php @@ -4,12 +4,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Integration\Controller\Adminhtml\Integration; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Integration\Controller\Adminhtml\Integration; use Magento\Integration\Model\Integration as IntegrationModel; -class TokensExchange extends \Magento\Integration\Controller\Adminhtml\Integration +class TokensExchange extends Integration implements HttpPostActionInterface { /** * Let the admin know that integration has been sent for activation and token exchange is in process. @@ -72,12 +75,12 @@ public function execute() ]; $this->getResponse()->representJson($this->jsonHelper->jsonEncode($result)); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_redirect('*/*'); return; } catch (\Exception $e) { $this->_logger->critical($e); - $this->messageManager->addError(__('Internal error. Check exception log for details.')); + $this->messageManager->addErrorMessage(__('Internal error. Check exception log for details.')); $this->_redirect('*/*'); return; } diff --git a/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/DeleteTest.php b/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/DeleteTest.php index aa1393be6534c..074c6ff2c2ae2 100644 --- a/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/DeleteTest.php +++ b/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/DeleteTest.php @@ -120,7 +120,7 @@ public function testDeleteActionConfigSetUp() ->willReturn(true); // verify error message $this->_messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('Uninstall the extension to remove integration \'%1\'.', $intData[Info::DATA_NAME])); $this->_integrationSvcMock->expects($this->never())->method('delete'); // Use real translate model @@ -143,7 +143,7 @@ public function testDeleteActionMissingId() $this->_translateModelMock = null; // verify error message $this->_messageManager->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with(__('Integration ID is not specified or is invalid.')); $this->integrationController->execute(); @@ -166,7 +166,7 @@ public function testDeleteActionForServiceIntegrationException() $this->_integrationSvcMock->expects($this->once()) ->method('delete') ->willThrowException($invalidIdException); - $this->_messageManager->expects($this->once())->method('addError'); + $this->_messageManager->expects($this->once())->method('addErrorMessage'); $this->integrationController->execute(); } @@ -188,7 +188,7 @@ public function testDeleteActionForServiceGenericException() $this->_integrationSvcMock->expects($this->once()) ->method('delete') ->willThrowException($invalidIdException); - $this->_messageManager->expects($this->never())->method('addError'); + $this->_messageManager->expects($this->never())->method('addErrorMessage'); $this->integrationController->execute(); } diff --git a/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/EditTest.php b/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/EditTest.php index 5cfa8b290b6b9..a9dc8ec616674 100644 --- a/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/EditTest.php +++ b/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/EditTest.php @@ -62,7 +62,7 @@ public function testEditActionNonExistentIntegration() { $exceptionMessage = 'This integration no longer exists.'; // verify the error - $this->_messageManager->expects($this->once())->method('addError')->with($exceptionMessage); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($exceptionMessage); $this->_requestMock->expects($this->any())->method('getParam')->willReturn(self::INTEGRATION_ID); // put data in session, the magic function getFormData is called so, must match __call method name $this->_backendSessionMock->expects( @@ -93,7 +93,7 @@ public function testEditActionNoDataAdd() { $exceptionMessage = 'Integration ID is not specified or is invalid.'; // verify the error - $this->_messageManager->expects($this->once())->method('addError')->with($exceptionMessage); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($exceptionMessage); $this->_verifyLoadAndRenderLayout(); $integrationContr = $this->_createIntegrationController('Edit'); $integrationContr->execute(); @@ -103,7 +103,7 @@ public function testEditException() { $exceptionMessage = 'Integration ID is not specified or is invalid.'; // verify the error - $this->_messageManager->expects($this->once())->method('addError')->with($exceptionMessage); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($exceptionMessage); $this->_controller = $this->_createIntegrationController('Edit'); $this->_controller->execute(); } diff --git a/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/SaveTest.php b/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/SaveTest.php index 8de8b45833043..f3b0c65b6a706 100644 --- a/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/SaveTest.php +++ b/app/code/Magento/Integration/Test/Unit/Controller/Adminhtml/Integration/SaveTest.php @@ -180,7 +180,7 @@ public function testSaveActionExceptionDuringServiceCreation() // Use real translate model $this->_translateModelMock = null; // Verify success message - $this->_messageManager->expects($this->once())->method('addError')->with($exceptionMessage); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($exceptionMessage); $integrationController = $this->_createIntegrationController('Save'); $integrationController->execute(); } @@ -211,7 +211,7 @@ public function testSaveActionExceptionOnIntegrationsCreatedFromConfigFile() ->willReturnArgument(0); // Verify error - $this->_messageManager->expects($this->once())->method('addError')->with($exceptionMessage); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($exceptionMessage); $integrationContr = $this->_createIntegrationController('Save'); $integrationContr->execute(); } @@ -283,7 +283,7 @@ public function testSaveActionAuthenticationException() ->willThrowException(new AuthenticationException(__($exceptionMessage))); // Verify error - $this->_messageManager->expects($this->once())->method('addError')->with($exceptionMessage); + $this->_messageManager->expects($this->once())->method('addErrorMessage')->with($exceptionMessage); $integrationContr = $this->_createIntegrationController('Save'); $integrationContr->execute(); } diff --git a/app/code/Magento/MediaContentSynchronization/Console/Command/Synchronize.php b/app/code/Magento/MediaContentSynchronization/Console/Command/Synchronize.php index 55f99697c289b..e591b4f2339b1 100644 --- a/app/code/Magento/MediaContentSynchronization/Console/Command/Synchronize.php +++ b/app/code/Magento/MediaContentSynchronization/Console/Command/Synchronize.php @@ -7,8 +7,6 @@ namespace Magento\MediaContentSynchronization\Console\Command; -use Magento\Framework\App\Area; -use Magento\Framework\App\State; use Magento\Framework\Console\Cli; use Magento\MediaContentSynchronizationApi\Api\SynchronizeInterface; use Symfony\Component\Console\Command\Command; @@ -25,21 +23,13 @@ class Synchronize extends Command */ private $synchronizeContent; - /** - * @var State $state - */ - private $state; - /** * @param SynchronizeInterface $synchronizeContent - * @param State $state */ public function __construct( - SynchronizeInterface $synchronizeContent, - State $state + SynchronizeInterface $synchronizeContent ) { $this->synchronizeContent = $synchronizeContent; - $this->state = $state; parent::__construct(); } @@ -58,12 +48,7 @@ protected function configure() protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln('Synchronizing content with assets...'); - $this->state->emulateAreaCode( - Area::AREA_ADMINHTML, - function () { - $this->synchronizeContent->execute(); - } - ); + $this->synchronizeContent->execute(); $output->writeln('Completed content synchronization.'); return Cli::RETURN_SUCCESS; } diff --git a/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php b/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php index f33c22a18b4b8..d0ba786c7084e 100644 --- a/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php +++ b/app/code/Magento/MediaGallery/Model/Directory/Command/CreateByPaths.php @@ -78,7 +78,7 @@ public function execute(array $paths): void if (!empty($failedPaths)) { throw new CouldNotSaveException( __( - 'Could not save directories: %paths', + 'Could not create directories: %paths', [ 'paths' => implode(' ,', $failedPaths) ] diff --git a/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetLinks.php b/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetLinks.php index eb6bd2aad236c..87f9359d4fc37 100644 --- a/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetLinks.php +++ b/app/code/Magento/MediaGallery/Model/ResourceModel/Keyword/SaveAssetLinks.php @@ -133,7 +133,7 @@ private function deleteAssetKeywords(int $assetId, array $obsoleteKeywordIds): v /** @var Mysql $connection */ $connection = $this->resourceConnection->getConnection(); $connection->delete( - $connection->getTableName( + $this->resourceConnection->getTableName( self::TABLE_ASSET_KEYWORD ), [ @@ -196,7 +196,7 @@ private function setAssetUpdatedAt(int $assetId): void try { $connection = $this->resourceConnection->getConnection(); $connection->update( - $connection->getTableName(self::TABLE_MEDIA_ASSET), + $this->resourceConnection->getTableName(self::TABLE_MEDIA_ASSET), ['updated_at' => null], ['id =?' => $assetId] ); diff --git a/app/code/Magento/MediaGallery/Test/Unit/Model/ResourceModel/Keyword/SaveAssetLinksTest.php b/app/code/Magento/MediaGallery/Test/Unit/Model/ResourceModel/Keyword/SaveAssetLinksTest.php index d027f0ed21b53..6531cddf628df 100644 --- a/app/code/Magento/MediaGallery/Test/Unit/Model/ResourceModel/Keyword/SaveAssetLinksTest.php +++ b/app/code/Magento/MediaGallery/Test/Unit/Model/ResourceModel/Keyword/SaveAssetLinksTest.php @@ -78,10 +78,14 @@ public function testAssetKeywordsSave(int $assetId, array $keywordIds, array $va $this->resourceConnectionMock->expects($this->exactly(2)) ->method('getConnection') ->willReturn($this->connectionMock); - $this->resourceConnectionMock->expects($this->once()) + $this->resourceConnectionMock->expects($this->any()) ->method('getTableName') - ->with('media_gallery_asset_keyword') - ->willReturn('prefix_media_gallery_asset_keyword'); + ->willReturnMap( + [ + ['media_gallery_asset_keyword', 'default', 'prefix_media_gallery_asset_keyword'], + ['media_gallery_asset', 'default', 'prefix_media_gallery_asset'] + ] + ); $this->connectionMock->expects($this->once()) ->method('insertArray') ->with( diff --git a/app/code/Magento/MediaGallery/etc/db_schema.xml b/app/code/Magento/MediaGallery/etc/db_schema.xml index 1001737daa8a7..1a9b0dc96a655 100644 --- a/app/code/Magento/MediaGallery/etc/db_schema.xml +++ b/app/code/Magento/MediaGallery/etc/db_schema.xml @@ -8,7 +8,7 @@ <schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd"> <table name="media_gallery_asset" resource="default" engine="innodb" comment="Media Gallery Asset"> <column xsi:type="int" name="id" unsigned="true" nullable="false" identity="true" comment="Entity ID"/> - <column xsi:type="varchar" name="path" length="255" nullable="true" comment="Path"/> + <column xsi:type="text" name="path" nullable="true" comment="Path"/> <column xsi:type="varchar" name="title" length="255" nullable="true" comment="Title"/> <column xsi:type="text" name="description" nullable="true" comment="Description"/> <column xsi:type="varchar" name="source" length="255" nullable="true" comment="Source"/> @@ -25,9 +25,6 @@ <index referenceId="MEDIA_GALLERY_ID" indexType="btree"> <column name="id"/> </index> - <constraint xsi:type="unique" referenceId="MEDIA_GALLERY_ID_PATH_TITLE_CONTENT_TYPE_WIDTH_HEIGHT"> - <column name="path"/> - </constraint> <index referenceId="MEDIA_GALLERY_ASSET_TITLE" indexType="fulltext"> <column name="title"/> </index> diff --git a/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json b/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json index b32dfbf082175..e958d630b7e3f 100644 --- a/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json +++ b/app/code/Magento/MediaGallery/etc/db_schema_whitelist.json @@ -20,7 +20,6 @@ "MEDIA_GALLERY_ASSET_TITLE": true }, "constraint": { - "MEDIA_GALLERY_ID_PATH_TITLE_CONTENT_TYPE_WIDTH_HEIGHT": true, "PRIMARY": true, "MEDIA_GALLERY_ASSET_PATH": true } diff --git a/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php b/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php new file mode 100644 index 0000000000000..f70d4584547a3 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Controller/Adminhtml/Product/GetSelected.php @@ -0,0 +1,84 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\MediaGalleryCatalogUi\Controller\Adminhtml\Product; + +use Magento\Framework\Controller\ResultInterface; +use Magento\Backend\App\Action\Context; +use Magento\Catalog\Api\ProductRepositoryInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Backend\App\Action; + +/** + * Returns selected product by product id. for ui-select filter + */ +class GetSelected extends Action implements HttpGetActionInterface +{ + /** + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Catalog::products'; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var ProductRepositoryInterface + */ + private $productRepository; + + /** + * GetSelected constructor. + * + * @param JsonFactory $jsonFactory + * @param ProductRepositoryInterface $productRepository + * @param Context $context + */ + public function __construct( + JsonFactory $jsonFactory, + ProductRepositoryInterface $productRepository, + Context $context + ) { + $this->resultJsonFactory = $jsonFactory; + $this->productRepository = $productRepository; + parent::__construct($context); + } + + /** + * Return selected products options + * + * @return ResultInterface + */ + public function execute() : ResultInterface + { + $productIds = $this->getRequest()->getParam('ids'); + $options = []; + + if (!is_array($productIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + } + foreach ($productIds as $id) { + try { + $product = $this->productRepository->getById($id); + $options[] = [ + 'value' => $product->getId(), + 'label' => $product->getName(), + 'is_active' => $product->getSatus(), + 'path' => $product->getSku() + ]; + } catch (\Exception $e) { + continue; + } + } + + return $this->resultJsonFactory->create()->setData($options); + } +} diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml deleted file mode 100644 index 0788bbd60291a..0000000000000 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertCategoryGridPageDetailsActionGroup.xml +++ /dev/null @@ -1,20 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - /** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ ---> -<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminAssertCategoryGridPageDetailsActionGroup"> - <annotations> - <description>Assert category grid page basic columns values for default category</description> - </annotations> - - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.path('1')}}" stepKey="assertPathColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name('1', 'Default Category')}}" stepKey="assertNameColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.displayMode('1', 'PRODUCTS')}}" stepKey="assertDisplayModeColumn"/> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.products('1', '0')}}" stepKey="assertProductsColumn"/> - </actionGroup> -</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceHolderGridActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceHolderGridActionGroup.xml new file mode 100644 index 0000000000000..e21fa89965391 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceHolderGridActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertMediaGalleryFilterPlaceHolderGridActionGroup"> + <annotations> + <description>Assert asset filter placeholder value</description> + </annotations> + <arguments> + <argument name="filterPlaceholder" type="string"/> + </arguments> + + <see selector="{{AdminProductGridFilterSection.enabledFilters}}" userInput="{{filterPlaceholder}}" stepKey="seeFilter"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminEditCategoryInGridPageActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminEditCategoryInGridPageActionGroup.xml index ccdebccab4e65..50ee9e890ad20 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminEditCategoryInGridPageActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminEditCategoryInGridPageActionGroup.xml @@ -11,7 +11,12 @@ <annotations> <description>Clicks the Edit action from the Media Gallery Category Grid</description> </annotations> - <click selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.edit('2', 'Edit')}}" stepKey="clickOnCategoryRow"/> + + <arguments> + <argument name="categoryName" type="string"/> + </arguments> + + <click selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.edit(categoryName, 'Edit')}}" stepKey="clickOnCategoryRow"/> <waitForPageLoad time="30" stepKey="waitForCategoryDetailsPageLoad"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml new file mode 100644 index 0000000000000..7c3a0165c28d0 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AdminSearchCategoryGridPageByCategoryNameActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminSearchCategoryGridPageByCategoryNameActionGroup"> + <annotations> + <description>Fills 'Search by category name' on Category Grid page. Clicks on Submit Search.</description> + </annotations> + <arguments> + <argument name="categoryName"/> + </arguments> + + <conditionalClick selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.clearFilters}}" dependentSelector="{{AdminMediaGalleryCatalogUiCategoryGridSection.clearFilters}}" visible="true" stepKey="clickClearFilters"/> + <fillField selector="{{AdminMediaGalleryCatalogUiCategoryGridSearchSection.searchInput}}" userInput="{{categoryName}}" stepKey="fillKeywordSearchField"/> + <click selector="{{AdminMediaGalleryCatalogUiCategoryGridSearchSection.submitSearch}}" stepKey="clickKeywordSearch"/> + <waitForLoadingMaskToDisappear stepKey="waitingForLoading" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml new file mode 100644 index 0000000000000..cec17bdbb1428 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageDetailsActionGroup.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminCategoryGridPageDetailsActionGroup"> + <arguments> + <argument name="category"/> + </arguments> + <annotations> + <description>Assert category grid page name and path column values for a specific category</description> + </annotations> + + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Name')}}" stepKey="grabNameColumnValue"/> + <assertEquals stepKey="assertNameColumn"> + <expectedResult type="string">$$category.name$$</expectedResult> + <actualResult type="variable">grabNameColumnValue</actualResult> + </assertEquals> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Path')}}" stepKey="grabPathColumnValue"/> + <assertStringContainsString stepKey="assertPathColumn"> + <expectedResult type="string">$$category.name$$</expectedResult> + <actualResult type="variable">grabPathColumnValue</actualResult> + </assertStringContainsString> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageImageColumnActionGroup.xml similarity index 59% rename from app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml rename to app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageImageColumnActionGroup.xml index 42d723f0811d3..b110ce44a8469 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageImageColumnActionGroup.xml @@ -5,17 +5,16 @@ * See COPYING.txt for license details. */ --> - <actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> - <actionGroup name="AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup"> - <annotations> - <description>Asserts category name in category grid page</description> - </annotations> + <actionGroup name="AssertAdminCategoryGridPageImageColumnActionGroup"> <arguments> - <argument name="categoryName" type="string"/> + <argument name="file" type="string" defaultValue="magento"/> </arguments> + <annotations> + <description>Assert category grid page image column a specific category</description> + </annotations> - <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.name('1', categoryName)}}" stepKey="assertNameColumn"/> - </actionGroup> + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.image(file)}}" stepKey="assertImageColumn"/> + </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml new file mode 100644 index 0000000000000..72b1bca56cb6e --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageNumberOfRecordsActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminCategoryGridPageNumberOfRecordsActionGroup"> + <arguments> + <argument name="numberOfRecords" type="string"/> + </arguments> + <annotations> + <description>Assert the number of records in the category grid page.</description> + </annotations> + + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSearchSection.numberOfRecordsFound}}" stepKey="grabNumberOfRecordsFound"/> + <assertEquals stepKey="assertStringIsEqual"> + <expectedResult type="string">{{numberOfRecords}}</expectedResult> + <actualResult type="variable">grabNumberOfRecordsFound</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup.xml new file mode 100644 index 0000000000000..e5d6f26e777fc --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup"> + <annotations> + <description>Assert category grid page products, in menu, and enabled column values for a specific category</description> + </annotations> + + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Products')}}" stepKey="grabProductsColumnValue"/> + <assertEquals stepKey="assertProductsColumn"> + <expectedResult type="string">0</expectedResult> + <actualResult type="variable">grabProductsColumnValue</actualResult> + </assertEquals> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('In Menu')}}" stepKey="grabInMenuColumnValue"/> + <assertEquals stepKey="assertInMenuColumn"> + <expectedResult type="string">Yes</expectedResult> + <actualResult type="variable">grabInMenuColumnValue</actualResult> + </assertEquals> + <grabTextFrom selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.columnValue('Enabled')}}" stepKey="grabEnabledColumnValue"/> + <assertEquals stepKey="assertEnabledColumn"> + <expectedResult type="string">Yes</expectedResult> + <actualResult type="variable">grabEnabledColumnValue</actualResult> + </assertEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup.xml new file mode 100644 index 0000000000000..c9c9a25d8a2a3 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/ActionGroup/AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup"> + <annotations> + <description>Assert asset filter placeholder value</description> + </annotations> + <arguments> + <argument name="filterPlaceholder" type="string"/> + </arguments> + + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.activeFilterPlaceholder(filterPlaceholder)}}" stepKey="assertFilterPLaceHolder" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Page/AdminMediaGalleryCatalogUiCategoryGridPage.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Page/AdminMediaGalleryCatalogUiCategoryGridPage.xml index 99cee48f443c7..59775dd148712 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Page/AdminMediaGalleryCatalogUiCategoryGridPage.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Page/AdminMediaGalleryCatalogUiCategoryGridPage.xml @@ -7,6 +7,7 @@ --> <pages xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd"> <page name="AdminMediaGalleryCatalogUiCategoryGridPage" url="media_gallery_catalog/category/index" area="admin" module="Magento_MediaGalleryCatalogUi"> + <section name="AdminMediaGalleryCatalogUiCategoryGridSearchSection"/> <section name="AdminMediaGalleryCatalogUiCategoryGridSection"/> </page> </pages> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSearchSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSearchSection.xml new file mode 100644 index 0000000000000..867721d1e42bb --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSearchSection.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminMediaGalleryCatalogUiCategoryGridSearchSection"> + <element name="searchInput" type="input" selector=".admin__data-grid-header input[placeholder='Search by category name']"/> + <element name="submitSearch" type="button" selector=".data-grid-search-control-wrap > button.action-submit" timeout="30"/> + <element name="numberOfRecordsFound" type="text" selector=".admin__data-grid-header .admin__control-support-text"/> + </section> +</sections> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml index 5267a215c8edd..f65ec84bc2ec8 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Section/AdminMediaGalleryCatalogUiCategoryGridSection.xml @@ -9,10 +9,10 @@ <sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> <section name="AdminMediaGalleryCatalogUiCategoryGridSection"> - <element name="path" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Path')]/preceding-sibling::th)]" parameterized="true"/> - <element name="name" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Name')]/preceding-sibling::th) +1 ]//*[text()='{{categoryName}}']" parameterized="true"/> - <element name="displayMode" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Display Mode')]/preceding-sibling::th) +1 ]//*[text()='{{productsText}}']" parameterized="true"/> - <element name="products" type="text" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Products')]/preceding-sibling::th) +1 ]//*[text()='{{productsQty}}']" parameterized="true"/> - <element name="edit" type="button" selector="//tr[{{row}}]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Action')]/preceding-sibling::th) +1 ]//*[text()='{{edit}}']" parameterized="true"/> + <element name="clearFilters" type="button" selector=".admin__data-grid-header [data-action='grid-filter-reset']" timeout="30"/> + <element name="activeFilterPlaceholder" type="text" selector="//div[@class='admin__current-filters-list-wrap']//li//span[contains(text(), '{{filterPlaceholder}}')]" parameterized="true"/> + <element name="image" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Image')]/preceding-sibling::th) +1]//img[contains(@src, '{{file}}')]" parameterized="true"/> + <element name="columnValue" type="text" selector="//tr//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., '{{columnName}}')]/preceding-sibling::th) +1 ]//div" parameterized="true"/> + <element name="edit" type="button" selector="//tr[td//text()[contains(., '{{categoryName}}')]]//td[count(//div[@data-role='grid-wrapper']//tr//th[contains(., 'Action')]/preceding-sibling::th) +1 ]//*[text()='{{actionButton}}']" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiEditCategoryGridPageTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiEditCategoryGridPageTest.xml index b20f63a005279..2a606d8ab6a9e 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiEditCategoryGridPageTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiEditCategoryGridPageTest.xml @@ -26,7 +26,9 @@ <deleteData createDataKey="category" stepKey="deleteCategory"/> </after> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryPage"/> - <actionGroup ref="AdminEditCategoryInGridPageActionGroup" stepKey="editCategoryItem"/> + <actionGroup ref="AdminEditCategoryInGridPageActionGroup" stepKey="editCategoryItem"> + <argument name="categoryName" value="$$category.name$$"/> + </actionGroup> <actionGroup ref="AdminAssertCategoryPageTitleActionGroup" stepKey="assertCategoryByName"/> </test> </tests> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml index d68fd4cb7cca8..d2a04a7d21d11 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiUsedInProductFilterTest.xml @@ -9,28 +9,26 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMediaGalleryCatalogUiUsedInProductFilterTest"> <annotations> + <skip> + <issueId value="https://github.com/magento/adobe-stock-integration/issues/1795"/> + </skip> <features value="AdminMediaGalleryUsedInProductsFilter"/> - <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1168"/> - <title value="Used in products filter"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1503"/> + <title value="User can open product entity the asset is associated"/> <stories value="Story 58: User sees entities where asset is used in" /> - <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1320712/scenarios/4951848"/> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/943908/scenarios/4523889"/> <description value="User filters assets used in products"/> <severity value="CRITICAL"/> <group value="media_gallery_ui"/> </annotations> <before> <magentoCLI command="config:set cms/wysiwyg/enabled enabled" stepKey="enableWYSIWYG"/> - <createData entity="SimpleSubCategory" stepKey="category"/> - <createData entity="SimpleProduct" stepKey="product"> - <requiredEntity createDataKey="category"/> - </createData> + <createData entity="SimpleProduct2" stepKey="product"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> <magentoCLI command="config:set cms/wysiwyg/enabled disabled" stepKey="disableWYSIWYG"/> <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> - <deleteData createDataKey="product" stepKey="deleteProduct"/> - <deleteData createDataKey="category" stepKey="deleteCategory"/> </after> <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchProduct"> <argument name="product" value="$$product$$"/> @@ -38,11 +36,7 @@ <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> <argument name="product" value="$$product$$"/> </actionGroup> - <click selector="{{AdminProductFormSection.contentTab}}" stepKey="clickContentTab"/> - <waitForElementVisible selector="{{CatalogWYSIWYGSection.TinyMCE4}}" stepKey="waitForTinyMCE4" /> - <click selector="{{CatalogWYSIWYGSection.InsertImageIcon}}" stepKey="clickInsertImageIcon" /> - <waitForPageLoad stepKey="waitForPageLoad" /> - <actionGroup ref="ClickBrowseBtnOnUploadPopupActionGroup" stepKey="clickBrowserBtn"/> + <actionGroup ref="AdminOpenMediaGalleryTinyMce4ActionGroup" stepKey="openMediaGalleryFromWysiwyg"/> <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> <argument name="image" value="ImageUpload3"/> </actionGroup> @@ -59,15 +53,28 @@ <argument name="optionName" value="$$product.name$$"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryApplyFiltersActionGroup" stepKey="applyFilters"/> - <actionGroup ref="AdminMediaGalleryAssertImageInGridActionGroup" stepKey="assertImageInGrid"> - <argument name="title" value="ImageMetadata.title"/> + + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetails"/> + <actionGroup ref="AdminEnhancedMediaGalleryClickEntityUsedInActionGroup" stepKey="clickUsedInProducts"> + <argument name="entityName" value="Products"/> + </actionGroup> + + <actionGroup ref="AdminAssertMediaGalleryFilterPlaceHolderGridActionGroup" stepKey="assertFilterApplied"> + <argument name="filterPlaceholder" value="$$product.name$$"/> </actionGroup> + + <deleteData createDataKey="product" stepKey="deleteProduct"/> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetailsToAssertEmptyUsedIn"/> + <actionGroup ref="AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup" stepKey="assertThereIsNoUsedInSection"/> + <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeDetails"/> + <actionGroup ref="AdminEnhancedMediaGalleryEnableMassActionModeActionGroup" stepKey="enableMassActionToDeleteImages"/> <actionGroup ref="AdminEnhancedMediaGallerySelectImageForMassActionActionGroup" stepKey="selectFirstImageToDelete"> <argument name="imageName" value="{{ImageMetadata.title}}"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> <actionGroup ref="AdminEnhancedMediaGalleryConfirmDeleteImagesActionGroup" stepKey="deleteImages"/> - + </test> </tests> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml index 6b7bd3ba11f45..fde9597155d0c 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyCategoryGridPageTest.xml @@ -26,6 +26,17 @@ <deleteData createDataKey="category" stepKey="deleteCategory"/> </after> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryPage"/> - <actionGroup ref="AdminAssertCategoryGridPageDetailsActionGroup" stepKey="assertCategoryGridPageRendered"/> + <actionGroup ref="AdminSearchCategoryGridPageByCategoryNameActionGroup" stepKey="searchByCategoryName"> + <argument name="categoryName" value="$$category.name$$"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> + <argument name="numberOfRecords" value="1 records found"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageImageColumnActionGroup" stepKey="assertCategoryGridPageImageColumn"/> + <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryGridPageRendered"> + <argument name="category" value="$$category$$"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup" stepKey="assertCategoryGridPageProductsInMenuEnabledColumns"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> </test> </tests> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml index e761ef5cd08ba..8191d5570f1e8 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest.xml @@ -9,6 +9,9 @@ <tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> <test name="AdminMediaGalleryCatalogUiVerifyUsedInLinkCategoryGridTest"> <annotations> + <skip> + <issueId value="https://github.com/magento/adobe-stock-integration/issues/1794"/> + </skip> <features value="AdminMediaGalleryCategoryGrid"/> <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1503"/> <title value="User can open each entity the asset is associated with in a separate tab to manage association"/> @@ -23,14 +26,12 @@ <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> </before> <after> - <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> <actionGroup ref="AdminEnhancedMediaGalleryEnableMassActionModeActionGroup" stepKey="enableMassActionToDeleteImages"/> <actionGroup ref="AdminEnhancedMediaGallerySelectImageForMassActionActionGroup" stepKey="selectSecondImageToDelete"> <argument name="imageName" value="{{UpdatedImageDetails.title}}"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> <actionGroup ref="AdminEnhancedMediaGalleryConfirmDeleteImagesActionGroup" stepKey="deleteImages"/> - <deleteData createDataKey="category" stepKey="deleteCategory"/> </after> <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="openCategoryPage"/> @@ -54,8 +55,38 @@ <actionGroup ref="AdminEnhancedMediaGalleryClickEntityUsedInActionGroup" stepKey="clickUsedInCategories"> <argument name="entityName" value="Categories"/> </actionGroup> - <actionGroup ref="AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup" stepKey="assertCategoryInGrid"> - <argument name="categoryName" value="$$category.name$$"/> + <actionGroup ref="AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup" stepKey="assertFilterApplied"> + <argument name="filterPlaceholder" value="{{UpdatedImageDetails.title}}"/> </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> + <argument name="numberOfRecords" value="1 records found"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageImageColumnActionGroup" stepKey="assertCategoryGridPageImageColumn"> + <argument name="file" value="{{UpdatedImageDetails.file}}"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryInGrid"> + <argument name="category" value="$$category$$"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup" stepKey="assertCategoryGridPageProductsInMenuEnabledColumns"/> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="firstResetAdminDataGridToDefaultView"/> + + <actionGroup ref="AdminEnhancedMediaGalleryCategoryGridExpandFilterActionGroup" stepKey="expandFilters"/> + <actionGroup ref="AdminEnhancedMediaGallerySelectUsedInFilterActionGroup" stepKey="setAssetFilter"> + <argument name="filterName" value="Asset"/> + <argument name="optionName" value="{{UpdatedImageDetails.title}}"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryCategoryGridApplyFiltersActionGroup" stepKey="applyFilters"/> + <actionGroup ref="AssertAdminMediaGalleryAssetFilterPlaceHolderActionGroup" stepKey="assertFilterAppliedAfterUrlFilterApplier"> + <argument name="filterPlaceholder" value="{{UpdatedImageDetails.title}}"/> + </actionGroup> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + + <deleteData createDataKey="category" stepKey="deleteCategory"/> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetailsToVerfifyEmptyUsedIn"/> + <actionGroup ref="AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup" stepKey="assertThereIsNoUsedInSection"/> + <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeDetails"/> + </test> </tests> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkProductGridTest.xml b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkProductGridTest.xml new file mode 100644 index 0000000000000..db7942d4c53bf --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Test/Mftf/Test/AdminMediaGalleryCatalogUiVerifyUsedInLinkProductGridTest.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGalleryCatalogUiVerifyUsedInLinkProductGridTest"> + <annotations> + <features value="AdminMediaGalleryUsedInProductsFilter"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1168"/> + <title value="Used in products filter"/> + <stories value="Story 58: User sees entities where asset is used in" /> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1320712/scenarios/4951848"/> + <description value="User filters assets used in products"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <magentoCLI command="config:set cms/wysiwyg/enabled enabled" stepKey="enableWYSIWYG"/> + <createData entity="SimpleSubCategory" stepKey="category"/> + <createData entity="SimpleProduct" stepKey="product"> + <requiredEntity createDataKey="category"/> + </createData> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <magentoCLI command="config:set cms/wysiwyg/enabled disabled" stepKey="disableWYSIWYG"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + </after> + <actionGroup ref="SearchForProductOnBackendActionGroup" stepKey="searchProduct"> + <argument name="product" value="$$product$$"/> + </actionGroup> + <actionGroup ref="OpenEditProductOnBackendActionGroup" stepKey="openEditProduct"> + <argument name="product" value="$$product$$"/> + </actionGroup> + <click selector="{{AdminProductFormSection.contentTab}}" stepKey="clickContentTab"/> + <waitForElementVisible selector="{{CatalogWYSIWYGSection.TinyMCE4}}" stepKey="waitForTinyMCE4" /> + <click selector="{{CatalogWYSIWYGSection.InsertImageIcon}}" stepKey="clickInsertImageIcon" /> + <waitForPageLoad stepKey="waitForPageLoad" /> + <actionGroup ref="ClickBrowseBtnOnUploadPopupActionGroup" stepKey="clickBrowserBtn"/> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload3"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryClickImageInGridActionGroup" stepKey="selectContentImageInGrid"> + <argument name="imageName" value="{{ImageMetadata.title}}"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryClickAddSelectedActionGroup" stepKey="clickAddSelectedContentImage"/> + <actionGroup ref="AdminMediaGalleryClickOkButtonTinyMce4ActionGroup" stepKey="clickOkButton"/> + <actionGroup ref="SaveProductFormActionGroup" stepKey="saveProduct"/> + + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGallery"/> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetails"/> + <actionGroup ref="AdminEnhancedMediaGalleryClickEntityUsedInActionGroup" stepKey="clickUsedInProducts"> + <argument name="entityName" value="Products"/> + </actionGroup> + <actionGroup ref="AdminAssertMediaGalleryFilterPlaceHolderGridActionGroup" stepKey="assertFilterApplied"> + <argument name="filterPlaceholder" value="{{ImageMetadata.title}}"/> + </actionGroup> + + <deleteData createDataKey="product" stepKey="deleteProduct"/> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> + + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetailsToVerfifyEmptyUsedIn"/> + <actionGroup ref="AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup" stepKey="assertThereIsNoUsedInSection"/> + <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeDetails"/> + <actionGroup ref="AdminEnhancedMediaGalleryEnableMassActionModeActionGroup" stepKey="enableMassActionToDeleteImages"/> + <actionGroup ref="AdminEnhancedMediaGallerySelectImageForMassActionActionGroup" stepKey="selectFirstImageToDelete"> + <argument name="imageName" value="{{ImageMetadata.title}}"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> + <actionGroup ref="AdminEnhancedMediaGalleryConfirmDeleteImagesActionGroup" stepKey="deleteImages"/> + + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php new file mode 100644 index 0000000000000..fe4720b4a3e60 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/InMenu.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns; + +use Magento\Ui\Component\Listing\Columns\Column; + +/** + * Class InMenu column for Category grid + */ +class InMenu extends Column +{ + /** + * Prepare data source. + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + if (isset($dataSource['data']['items'])) { + $fieldName = $this->getData('name'); + foreach ($dataSource['data']['items'] as & $item) { + if (isset($item[$fieldName]) && $item[$fieldName] == 1) { + $item[$fieldName] = 'Yes'; + } else { + $item[$fieldName] = 'No'; + } + } + } + + return $dataSource; + } +} diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php new file mode 100644 index 0000000000000..c6f20c937d5b3 --- /dev/null +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/IsActive.php @@ -0,0 +1,36 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns; + +use Magento\Ui\Component\Listing\Columns\Column; + +/** + * Class IsActive column for Category grid + */ +class IsActive extends Column +{ + /** + * Prepare data source. + * + * @param array $dataSource + * @return array + */ + public function prepareDataSource(array $dataSource) + { + if (isset($dataSource['data']['items'])) { + $fieldName = $this->getData('name'); + foreach ($dataSource['data']['items'] as & $item) { + if (isset($item[$fieldName]) && $item[$fieldName] == 1) { + $item[$fieldName] = 'Yes'; + } else { + $item[$fieldName] = 'No'; + } + } + } + + return $dataSource; + } +} diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php index efb2ad2f8dae5..dada8ee7acc19 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php +++ b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Columns/Thumbnail.php @@ -5,8 +5,9 @@ */ namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns; -use Magento\Catalog\Helper\Image; -use Magento\Framework\DataObject; +use Magento\Catalog\Model\Category\Image; +use Magento\Catalog\Model\CategoryRepository; +use Magento\Framework\View\Asset\Repository as AssetRepository; use Magento\Framework\View\Element\UiComponent\ContextInterface; use Magento\Framework\View\Element\UiComponentFactory; use Magento\Store\Model\Store; @@ -27,13 +28,32 @@ class Thumbnail extends Column /** * @var Image */ - private $imageHelper; + private $categoryImage; /** + * @var CategoryRepository + */ + private $categoryRepository; + + /** + * @var AssetRepository + */ + private $assetRepository; + + /** + * @var string[] + */ + private $defaultPlaceholder; + + /** + * Thumbnail constructor. * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param StoreManagerInterface $storeManager - * @param Image $image + * @param Image $categoryImage + * @param CategoryRepository $categoryRepository + * @param AssetRepository $assetRepository + * @param array $defaultPlaceholder * @param array $components * @param array $data */ @@ -41,13 +61,19 @@ public function __construct( ContextInterface $context, UiComponentFactory $uiComponentFactory, StoreManagerInterface $storeManager, - Image $image, + Image $categoryImage, + CategoryRepository $categoryRepository, + AssetRepository $assetRepository, + array $defaultPlaceholder = [], array $components = [], array $data = [] ) { parent::__construct($context, $uiComponentFactory, $components, $data); - $this->imageHelper = $image; $this->storeManager = $storeManager; + $this->categoryImage = $categoryImage; + $this->categoryRepository = $categoryRepository; + $this->assetRepository = $assetRepository; + $this->defaultPlaceholder = $defaultPlaceholder; } /** @@ -55,20 +81,34 @@ public function __construct( * * @param array $dataSource * @return array + * @throws \Magento\Framework\Exception\LocalizedException + * @throws \Magento\Framework\Exception\NoSuchEntityException */ public function prepareDataSource(array $dataSource) { - if (isset($dataSource['data']['items'])) { - $fieldName = $this->getData('name'); - foreach ($dataSource['data']['items'] as & $item) { - if (isset($item[$fieldName])) { - $item[$fieldName . '_src'] = $this->getUrl($item[$fieldName]); - } else { - $category = new DataObject($item); - $imageHelper = $this->imageHelper->init($category, 'product_listing_thumbnail'); - $item[$fieldName . '_src'] = $imageHelper->getUrl(); + if (!isset($dataSource['data']['items'])) { + return $dataSource; + } + + $fieldName = $this->getData('name'); + foreach ($dataSource['data']['items'] as & $item) { + if (isset($item[$fieldName])) { + $item[$fieldName . '_src'] = $this->getUrl($item[$fieldName]); + continue; + } + + if (isset($item['entity_id'])) { + $src = $this->categoryImage->getUrl( + $this->categoryRepository->get($item['entity_id']) + ); + + if (!empty($src)) { + $item[$fieldName . '_src'] = $src; + continue; } } + + $item[$fieldName . '_src'] = $this->assetRepository->getUrl($this->defaultPlaceholder['image']); } return $dataSource; diff --git a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php b/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php deleted file mode 100644 index 254ebd047c954..0000000000000 --- a/app/code/Magento/MediaGalleryCatalogUi/Ui/Component/Listing/Filters/UsedInProducts.php +++ /dev/null @@ -1,133 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Filters; - -use Magento\Framework\Api\FilterBuilder; -use Magento\Framework\Data\OptionSourceInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Ui\Component\Filters\FilterModifier; -use Magento\Ui\Component\Filters\Type\Select; -use Magento\Ui\Api\BookmarkManagementInterface; -use Magento\Catalog\Api\ProductRepositoryInterface; - -/** - * Used in products filter - */ -class UsedInProducts extends Select -{ - /** - * @var BookmarkManagementInterface - */ - private $bookmarkManagement; - - /** - * @var ProductRepositoryInterface - */ - private $productRepository; - - /** - * Constructor - * - * @param ContextInterface $context - * @param UiComponentFactory $uiComponentFactory - * @param FilterBuilder $filterBuilder - * @param FilterModifier $filterModifier - * @param OptionSourceInterface $optionsProvider - * @param BookmarkManagementInterface $bookmarkManagement - * @param ProductRepositoryInterface $productRepository - * @param array $components - * @param array $data - */ - public function __construct( - ContextInterface $context, - UiComponentFactory $uiComponentFactory, - FilterBuilder $filterBuilder, - FilterModifier $filterModifier, - OptionSourceInterface $optionsProvider = null, - BookmarkManagementInterface $bookmarkManagement, - ProductRepositoryInterface $productRepository, - array $components = [], - array $data = [] - ) { - $this->uiComponentFactory = $uiComponentFactory; - $this->filterBuilder = $filterBuilder; - parent::__construct( - $context, - $uiComponentFactory, - $filterBuilder, - $filterModifier, - $optionsProvider, - $components, - $data - ); - $this->bookmarkManagement = $bookmarkManagement; - $this->productRepository = $productRepository; - } - - /** - * Prepare component configuration - * - * @return void - */ - public function prepare() - { - $options = []; - $productIds = []; - $bookmarks = $this->bookmarkManagement->loadByNamespace($this->context->getNameSpace())->getItems(); - foreach ($bookmarks as $bookmark) { - if ($bookmark->getIdentifier() === 'current') { - $applied = $bookmark->getConfig()['current']['filters']['applied']; - if (isset($applied[$this->getName()])) { - $productIds = $applied[$this->getName()]; - } - } - } - - foreach ($productIds as $id) { - $product = $this->productRepository->getById($id); - $options[] = [ - 'value' => $id, - 'label' => $product->getName(), - 'is_active' => $product->getStatus(), - 'path' => $product->getSku(), - 'optgroup' => false - - ]; - } - - $this->wrappedComponent = $this->uiComponentFactory->create( - $this->getName(), - parent::COMPONENT, - [ - 'context' => $this->getContext(), - 'options' => $options - ] - ); - - $this->wrappedComponent->prepare(); - $productsFilterJsConfig = array_replace_recursive( - $this->getJsConfig($this->wrappedComponent), - $this->getJsConfig($this) - ); - $this->setData('js_config', $productsFilterJsConfig); - - $this->setData( - 'config', - array_replace_recursive( - (array)$this->wrappedComponent->getData('config'), - (array)$this->getData('config') - ) - ); - - $this->applyFilter(); - - parent::prepare(); - } -} diff --git a/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml b/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml index ae01c29928b4a..2aaf5a56cf837 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/etc/adminhtml/di.xml @@ -38,4 +38,11 @@ </argument> </arguments> </type> + <type name="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\Thumbnail"> + <arguments> + <argument name="defaultPlaceholder" xsi:type="array"> + <item name="image" xsi:type="string">Magento_MediaGalleryCatalogUi::images/category/placeholder/image.jpg</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml index 9945643ccffef..17fe33e5b2bf5 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_category_listing.xml @@ -58,7 +58,7 @@ provider="${ $.parentName }" sortOrder="10" class="Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="Magento_MediaGalleryUi/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -74,6 +74,7 @@ <item name="filterRateLimitMethod" xsi:type="string" translate="true">notifyWhenChangesStop</item> <item name="searchOptions" xsi:type="boolean">true</item> <item name="searchUrl" xsi:type="url" path="media_gallery/asset/search" /> + <item name="validationUrl" xsi:type="url" path="media_gallery/asset/getSelected"/> <item name="levelsVisibility" xsi:type="number">1</item> </item> </argument> @@ -167,12 +168,12 @@ <label translate="true">Products</label> </settings> </column> - <column name="include_in_menu" component="Magento_Ui/js/grid/columns/select"> + <column name="include_in_menu" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\InMenu"> <settings> <label translate="true">In Menu</label> </settings> </column> - <column name="is_active" component="Magento_Ui/js/grid/columns/select" > + <column name="is_active" class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Columns\IsActive"> <settings> <label translate="true">Enabled</label> </settings> diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml index 2ca58b6020fa7..6976584c2e36c 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/media_gallery_listing.xml @@ -13,8 +13,7 @@ name="product_id" provider="${ $.parentName }" sortOrder="110" - class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Filters\UsedInProducts" - component="Magento_Catalog/js/components/product-ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -30,7 +29,7 @@ <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> <item name="levelsVisibility" xsi:type="number">1</item> <item name="searchUrl" xsi:type="url" path="catalog/product/search"/> - <item name="validationUrl" xsi:type="url" path="catalog/product/getSelected"/> + <item name="validationUrl" xsi:type="url" path="media_gallery_catalog/product/getSelected"/> </item> </argument> <settings> diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml index 2ca58b6020fa7..6976584c2e36c 100644 --- a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml @@ -13,8 +13,7 @@ name="product_id" provider="${ $.parentName }" sortOrder="110" - class="Magento\MediaGalleryCatalogUi\Ui\Component\Listing\Filters\UsedInProducts" - component="Magento_Catalog/js/components/product-ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -30,7 +29,7 @@ <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> <item name="levelsVisibility" xsi:type="number">1</item> <item name="searchUrl" xsi:type="url" path="catalog/product/search"/> - <item name="validationUrl" xsi:type="url" path="catalog/product/getSelected"/> + <item name="validationUrl" xsi:type="url" path="media_gallery_catalog/product/getSelected"/> </item> </argument> <settings> diff --git a/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/web/images/category/placeholder/image.jpg b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/web/images/category/placeholder/image.jpg new file mode 100644 index 0000000000000..0d5ef7e1bd412 Binary files /dev/null and b/app/code/Magento/MediaGalleryCatalogUi/view/adminhtml/web/images/category/placeholder/image.jpg differ diff --git a/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php new file mode 100644 index 0000000000000..a686f0e7b3ace --- /dev/null +++ b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Block/GetSelected.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryCmsUi\Controller\Adminhtml\Block; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Cms\Api\BlockRepositoryInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Controller\ResultInterface; + +/** + * Controller to get selected block for ui-select component + */ +class GetSelected extends Action implements HttpGetActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Cms::block'; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var BlockRepositoryInterface + */ + private $blockRepository; + + /** + * @param JsonFactory $resultFactory + * @param BlockRepositoryInterface $blockRepository + * @param Context $context + */ + public function __construct( + JsonFactory $resultFactory, + BlockRepositoryInterface $blockRepository, + Context $context + ) { + $this->resultJsonFactory = $resultFactory; + $this->blockRepository = $blockRepository; + parent::__construct($context); + } + + /** + * Return selected blocks options. + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + $options = []; + $blockIds = $this->getRequest()->getParam('ids'); + + if (!is_array($blockIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + } + foreach ($blockIds as $id) { + try { + $block = $this->blockRepository->getById($id); + $options[] = [ + 'value' => $block->getId(), + 'label' => $block->getTitle(), + 'is_active' => $block->isActive(), + 'optgroup' => false + ]; + } catch (\Exception $e) { + continue; + } + } + + return $this->resultJsonFactory->create()->setData($options); + } +} diff --git a/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php new file mode 100644 index 0000000000000..be6eb9fd9de9f --- /dev/null +++ b/app/code/Magento/MediaGalleryCmsUi/Controller/Adminhtml/Page/GetSelected.php @@ -0,0 +1,83 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryCmsUi\Controller\Adminhtml\Page; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Cms\Api\PageRepositoryInterface; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Controller\ResultInterface; + +/** + * Controller to get selected page for ui-select component + */ +class GetSelected extends Action implements HttpGetActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Cms::page'; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var PageRepositoryInterface + */ + private $pageRepository; + + /** + * @param JsonFactory $resultFactory + * @param PageRepositoryInterface $pageRepository + * @param Context $context + */ + public function __construct( + JsonFactory $resultFactory, + PageRepositoryInterface $pageRepository, + Context $context + ) { + $this->resultJsonFactory = $resultFactory; + $this->pageRepository = $pageRepository; + parent::__construct($context); + } + + /** + * Return selected pages options. + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + $options = []; + $pageIds = $this->getRequest()->getParam('ids'); + + if (!is_array($pageIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + } + foreach ($pageIds as $id) { + try { + $page = $this->pageRepository->getById($id); + $options[] = [ + 'value' => $page->getId(), + 'label' => $page->getTitle(), + 'is_active' => $page->isActive(), + 'optgroup' => false + ]; + } catch (\Exception $e) { + continue; + } + } + + return $this->resultJsonFactory->create()->setData($options); + } +} diff --git a/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryAssertUsedInLinkBlocksGridTest.xml b/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryAssertUsedInLinkBlocksGridTest.xml new file mode 100644 index 0000000000000..a0cd04fad54c5 --- /dev/null +++ b/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryAssertUsedInLinkBlocksGridTest.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGalleryAssertUsedInLinkBlocksGridTest"> + <annotations> + <features value="AdminMediaGalleryUsedInBlocksFilter"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1168"/> + <title value="Used in blocks link"/> + <stories value="Story 58: User sees entities where asset is used in" /> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1320712/scenarios/4951848"/> + <description value="User filters assets used in blocks"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <createData entity="_defaultBlock" stepKey="block" /> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + </before> + <after> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + </after> + + <actionGroup ref="NavigateToCreatedCMSBlockPageActionGroup" stepKey="navigateToCreatedCMSBlockPage1"> + <argument name="CMSBlockPage" value="$$block$$"/> + </actionGroup> + <click selector="{{CmsWYSIWYGSection.InsertImageBtn}}" stepKey="clickInsertImageIcon" /> + <waitForPageLoad stepKey="waitForPageLoad" /> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload3"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryClickImageInGridActionGroup" stepKey="selectContentImageInGrid"> + <argument name="imageName" value="{{ImageMetadata.title}}"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryClickAddSelectedActionGroup" stepKey="clickAddSelectedContentImage"/> + <click selector="{{BlockNewPagePageActionsSection.saveBlock}}" stepKey="saveBlock"/> + + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGallery"/> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetails"/> + <actionGroup ref="AdminEnhancedMediaGalleryClickEntityUsedInActionGroup" stepKey="clickUsedInPages"> + <argument name="entityName" value="Blocks"/> + </actionGroup> + <actionGroup ref="AdminAssertMediaGalleryFilterPlaceHolderGridActionGroup" stepKey="assertFilterApplied"> + <argument name="filterPlaceholder" value="{{ImageMetadata.title}}"/> + </actionGroup> + + <deleteData createDataKey="block" stepKey="deleteBlock"/> + + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetailsToVerfifyEmptyUsedIn"/> + <actionGroup ref="AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup" stepKey="assertThereIsNoUsedInSection"/> + <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeDetails"/> + + <actionGroup ref="AdminEnhancedMediaGalleryEnableMassActionModeActionGroup" stepKey="enableMassActionToDeleteImages"/> + <actionGroup ref="AdminEnhancedMediaGallerySelectImageForMassActionActionGroup" stepKey="selectFirstImageToDelete"> + <argument name="imageName" value="{{ImageMetadata.title}}"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> + <actionGroup ref="AdminEnhancedMediaGalleryConfirmDeleteImagesActionGroup" stepKey="deleteImages"/> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryAssertUsedInLinkPagesGridTest.xml b/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryAssertUsedInLinkPagesGridTest.xml new file mode 100644 index 0000000000000..de8517eedae0e --- /dev/null +++ b/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryAssertUsedInLinkPagesGridTest.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGalleryAssertUsedInLinkPagesGridTest"> + <annotations> + <features value="AdminMediaGalleryUsedInBlocksFilter"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1168"/> + <title value="Used in pages link"/> + <stories value="Story 58: User sees entities where asset is used in" /> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1320712/scenarios/4951848"/> + <description value="User filters assets used in pages"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> + </before> + <after> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + </after> + + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToCreateNewPage"/> + <actionGroup ref="FillOutCustomCMSPageContentActionGroup" stepKey="fillBasicPageDataForPageWithDefaultStore"> + <argument name="title" value="Unique page title MediaGalleryUi"/> + <argument name="content" value="MediaGalleryUI content"/> + <argument name="identifier" value="test-page-1"/> + </actionGroup> + + <actionGroup ref="AdminOpenMediaGalleryFromPageNoEditorActionGroup" stepKey="openMediaGalleryForPage"/> + <waitForPageLoad stepKey="waitForPageLoad" /> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload3"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryClickImageInGridActionGroup" stepKey="selectContentImageInGrid"> + <argument name="imageName" value="{{ImageMetadata.title}}"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryClickAddSelectedActionGroup" stepKey="clickAddSelectedContentImage"/> + <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="savePage"/> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGallery"/> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetails"/> + <actionGroup ref="AdminEnhancedMediaGalleryClickEntityUsedInActionGroup" stepKey="clickUsedInPages"> + <argument name="entityName" value="Pages"/> + </actionGroup> + <actionGroup ref="AdminAssertMediaGalleryFilterPlaceHolderGridActionGroup" stepKey="assertFilterApplied"> + <argument name="filterPlaceholder" value="{{ImageMetadata.title}}"/> + </actionGroup> + + <actionGroup ref="AdminDeleteCmsPageFromGridActionGroup" stepKey="deleteCmsPage"> + <argument name="urlKey" value="test-page-1"/> + </actionGroup> + + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="openViewImageDetailsToVerfifyEmptyUsedIn"/> + <actionGroup ref="AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup" stepKey="assertThereIsNoUsedInSection"/> + <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeDetails"/> + + <actionGroup ref="AdminEnhancedMediaGalleryEnableMassActionModeActionGroup" stepKey="enableMassActionToDeleteImages"/> + <actionGroup ref="AdminEnhancedMediaGallerySelectImageForMassActionActionGroup" stepKey="selectFirstImageToDelete"> + <argument name="imageName" value="{{ImageMetadata.title}}"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> + <actionGroup ref="AdminEnhancedMediaGalleryConfirmDeleteImagesActionGroup" stepKey="deleteImages"/> + + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryCmsUiUsedInBlocksFilterTest.xml b/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryCmsUiUsedInBlocksFilterTest.xml index 810d9eea4e261..fa6dc6c1a07fa 100644 --- a/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryCmsUiUsedInBlocksFilterTest.xml +++ b/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryCmsUiUsedInBlocksFilterTest.xml @@ -55,6 +55,6 @@ </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> <actionGroup ref="AdminEnhancedMediaGalleryConfirmDeleteImagesActionGroup" stepKey="deleteImages"/> - + </test> </tests> diff --git a/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryCmsUiUsedInPagesFilterTest.xml b/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryCmsUiUsedInPagesFilterTest.xml index a6bfdb781a734..038f1ae077b4a 100644 --- a/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryCmsUiUsedInPagesFilterTest.xml +++ b/app/code/Magento/MediaGalleryCmsUi/Test/Mftf/Test/AdminMediaGalleryCmsUiUsedInPagesFilterTest.xml @@ -21,14 +21,14 @@ <before> <actionGroup ref="AdminLoginActionGroup" stepKey="login"/> </before> - + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToCreateNewPage"/> <actionGroup ref="FillOutCustomCMSPageContentActionGroup" stepKey="fillBasicPageDataForPageWithDefaultStore"> <argument name="title" value="Unique page title MediaGalleryUi"/> <argument name="content" value="MediaGalleryUI content"/> <argument name="identifier" value="test-page-1"/> </actionGroup> - + <actionGroup ref="AdminOpenMediaGalleryFromPageNoEditorActionGroup" stepKey="openMediaGalleryForPage"/> <waitForPageLoad stepKey="waitForPageLoad" /> <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> @@ -39,7 +39,7 @@ </actionGroup> <actionGroup ref="AdminMediaGalleryClickAddSelectedActionGroup" stepKey="clickAddSelectedContentImage"/> <click selector="{{CmsNewPagePageActionsSection.saveAndContinueEdit}}" stepKey="savePage"/> - + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGallery"/> <actionGroup ref="AdminEnhancedMediaGalleryExpandFilterActionGroup" stepKey="expandFilters"/> <actionGroup ref="AdminEnhancedMediaGallerySelectUsedInFilterActionGroup" stepKey="setUsedInFilter"> @@ -56,8 +56,9 @@ <argument name="imageName" value="{{ImageMetadata.title}}"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> - + <actionGroup ref="AdminNavigateToPageGridActionGroup" stepKey="navigateToCmsPageGrid"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearGridFilters"/> <actionGroup ref="AdminSearchCmsPageInGridByUrlKeyActionGroup" stepKey="findCreatedCmsPage"> <argument name="urlKey" value="test-page-1"/> </actionGroup> diff --git a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php b/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php deleted file mode 100644 index 09fea24c8a2a9..0000000000000 --- a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInBlocks.php +++ /dev/null @@ -1,131 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters; - -use Magento\Framework\Api\FilterBuilder; -use Magento\Framework\Data\OptionSourceInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Ui\Component\Filters\FilterModifier; -use Magento\Ui\Component\Filters\Type\Select; -use Magento\Ui\Api\BookmarkManagementInterface; -use Magento\Cms\Api\BlockRepositoryInterface; - -/** - * Used in blocks filter - */ -class UsedInBlocks extends Select -{ - /** - * @var BookmarkManagementInterface - */ - private $bookmarkManagement; - - /** - * @var BlockRepositoryInterface - */ - private $blockRepository; - - /** - * Constructor - * - * @param ContextInterface $context - * @param UiComponentFactory $uiComponentFactory - * @param FilterBuilder $filterBuilder - * @param FilterModifier $filterModifier - * @param OptionSourceInterface $optionsProvider - * @param BookmarkManagementInterface $bookmarkManagement - * @param BlockRepositoryInterface $blockRepository - * @param array $components - * @param array $data - */ - public function __construct( - ContextInterface $context, - UiComponentFactory $uiComponentFactory, - FilterBuilder $filterBuilder, - FilterModifier $filterModifier, - OptionSourceInterface $optionsProvider = null, - BookmarkManagementInterface $bookmarkManagement, - BlockRepositoryInterface $blockRepository, - array $components = [], - array $data = [] - ) { - $this->uiComponentFactory = $uiComponentFactory; - $this->filterBuilder = $filterBuilder; - parent::__construct( - $context, - $uiComponentFactory, - $filterBuilder, - $filterModifier, - $optionsProvider, - $components, - $data - ); - $this->bookmarkManagement = $bookmarkManagement; - $this->blockRepository = $blockRepository; - } - - /** - * Prepare component configuration - * - * @return void - */ - public function prepare() - { - $options = []; - $blockIds = []; - $bookmarks = $this->bookmarkManagement->loadByNamespace($this->context->getNameSpace())->getItems(); - foreach ($bookmarks as $bookmark) { - if ($bookmark->getIdentifier() === 'current') { - $applied = $bookmark->getConfig()['current']['filters']['applied']; - if (isset($applied[$this->getName()])) { - $blockIds = $applied[$this->getName()]; - } - } - } - - foreach ($blockIds as $id) { - $block = $this->blockRepository->getById($id); - $options[] = [ - 'value' => $id, - 'label' => $block->getTitle(), - 'is_active' => $block->isActive(), - 'optgroup' => false - ]; - } - - $this->wrappedComponent = $this->uiComponentFactory->create( - $this->getName(), - parent::COMPONENT, - [ - 'context' => $this->getContext(), - 'options' => $options - ] - ); - - $this->wrappedComponent->prepare(); - $jsConfig = array_replace_recursive( - $this->getJsConfig($this->wrappedComponent), - $this->getJsConfig($this) - ); - $this->setData('js_config', $jsConfig); - - $this->setData( - 'config', - array_replace_recursive( - (array)$this->wrappedComponent->getData('config'), - (array)$this->getData('config') - ) - ); - - $this->applyFilter(); - - parent::prepare(); - } -} diff --git a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php b/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php deleted file mode 100644 index 235a77cdcb8c5..0000000000000 --- a/app/code/Magento/MediaGalleryCmsUi/Ui/Component/Listing/Filters/UsedInPages.php +++ /dev/null @@ -1,131 +0,0 @@ -<?php -/** - * Copyright © Magento, Inc. All rights reserved. - * See COPYING.txt for license details. - */ - -declare(strict_types=1); - -namespace Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters; - -use Magento\Framework\Api\FilterBuilder; -use Magento\Framework\Data\OptionSourceInterface; -use Magento\Framework\View\Element\UiComponent\ContextInterface; -use Magento\Framework\View\Element\UiComponentFactory; -use Magento\Ui\Component\Filters\FilterModifier; -use Magento\Ui\Component\Filters\Type\Select; -use Magento\Ui\Api\BookmarkManagementInterface; -use Magento\Cms\Api\PageRepositoryInterface; - -/** - * Used in pages filter - */ -class UsedInPages extends Select -{ - /** - * @var BookmarkManagementInterface - */ - private $bookmarkManagement; - - /** - * @var PageRepositoryInterface - */ - private $pageRepository; - - /** - * Constructor - * - * @param ContextInterface $context - * @param UiComponentFactory $uiComponentFactory - * @param FilterBuilder $filterBuilder - * @param FilterModifier $filterModifier - * @param OptionSourceInterface $optionsProvider - * @param BookmarkManagementInterface $bookmarkManagement - * @param PageRepositoryInterface $pageRepository - * @param array $components - * @param array $data - */ - public function __construct( - ContextInterface $context, - UiComponentFactory $uiComponentFactory, - FilterBuilder $filterBuilder, - FilterModifier $filterModifier, - OptionSourceInterface $optionsProvider = null, - BookmarkManagementInterface $bookmarkManagement, - PageRepositoryInterface $pageRepository, - array $components = [], - array $data = [] - ) { - $this->uiComponentFactory = $uiComponentFactory; - $this->filterBuilder = $filterBuilder; - parent::__construct( - $context, - $uiComponentFactory, - $filterBuilder, - $filterModifier, - $optionsProvider, - $components, - $data - ); - $this->bookmarkManagement = $bookmarkManagement; - $this->pageRepository = $pageRepository; - } - - /** - * Prepare component configuration - * - * @return void - */ - public function prepare() - { - $options = []; - $pageIds = []; - $bookmarks = $this->bookmarkManagement->loadByNamespace($this->context->getNameSpace())->getItems(); - foreach ($bookmarks as $bookmark) { - if ($bookmark->getIdentifier() === 'current') { - $applied = $bookmark->getConfig()['current']['filters']['applied']; - if (isset($applied[$this->getName()])) { - $pageIds = $applied[$this->getName()]; - } - } - } - - foreach ($pageIds as $id) { - $page = $this->pageRepository->getById($id); - $options[] = [ - 'value' => $id, - 'label' => $page->getTitle(), - 'is_active' => $page->isActive(), - 'optgroup' => false - ]; - } - - $this->wrappedComponent = $this->uiComponentFactory->create( - $this->getName(), - parent::COMPONENT, - [ - 'context' => $this->getContext(), - 'options' => $options - ] - ); - - $this->wrappedComponent->prepare(); - $pagesFilterjsConfig = array_replace_recursive( - $this->getJsConfig($this->wrappedComponent), - $this->getJsConfig($this) - ); - $this->setData('js_config', $pagesFilterjsConfig); - - $this->setData( - 'config', - array_replace_recursive( - (array)$this->wrappedComponent->getData('config'), - (array)$this->getData('config') - ) - ); - - $this->applyFilter(); - - parent::prepare(); - } -} diff --git a/app/code/Magento/MediaGalleryCmsUi/composer.json b/app/code/Magento/MediaGalleryCmsUi/composer.json index 73747a669c051..1ecfb9a3c8855 100644 --- a/app/code/Magento/MediaGalleryCmsUi/composer.json +++ b/app/code/Magento/MediaGalleryCmsUi/composer.json @@ -5,8 +5,7 @@ "php": "~7.3.0||~7.4.0", "magento/framework": "*", "magento/module-cms": "*", - "magento/module-backend": "*", - "magento/module-ui": "*" + "magento/module-backend": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml index 506a6cad5b68e..e49ba7a98c8ce 100644 --- a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/media_gallery_listing.xml @@ -13,8 +13,7 @@ name="page_id" provider="${ $.parentName }" sortOrder="120" - class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInPages" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -27,6 +26,7 @@ <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find pages</item> <item name="filterRateLimit" xsi:type="string" translate="true">1000</item> <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> + <item name="validationUrl" xsi:type="url" path="media_gallery_cms/page/getSelected"/> </item> </argument> <settings> @@ -38,8 +38,7 @@ name="block_id" provider="${ $.parentName }" sortOrder="130" - class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInBlocks" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -52,6 +51,7 @@ <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find blocks</item> <item name="filterRateLimit" xsi:type="string" translate="true">1000</item> <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> + <item name="validationUrl" xsi:type="url" path="media_gallery_cms/block/getSelected"/> </item> </argument> <settings> diff --git a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml index 506a6cad5b68e..e49ba7a98c8ce 100644 --- a/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryCmsUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml @@ -13,8 +13,7 @@ name="page_id" provider="${ $.parentName }" sortOrder="120" - class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInPages" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -27,6 +26,7 @@ <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find pages</item> <item name="filterRateLimit" xsi:type="string" translate="true">1000</item> <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> + <item name="validationUrl" xsi:type="url" path="media_gallery_cms/page/getSelected"/> </item> </argument> <settings> @@ -38,8 +38,7 @@ name="block_id" provider="${ $.parentName }" sortOrder="130" - class="Magento\MediaGalleryCmsUi\Ui\Component\Listing\Filters\UsedInBlocks" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="ui/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -52,6 +51,7 @@ <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find blocks</item> <item name="filterRateLimit" xsi:type="string" translate="true">1000</item> <item name="filterRateLimitMethod" xsi:type="string">notifyWhenChangesStop</item> + <item name="validationUrl" xsi:type="url" path="media_gallery_cms/block/getSelected"/> </item> </argument> <settings> diff --git a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php similarity index 57% rename from app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php rename to app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php index 317b811df5692..ed8108f012af0 100644 --- a/app/code/Magento/MediaGalleryIntegration/Model/OpenDialogUrlProvider.php +++ b/app/code/Magento/MediaGalleryIntegration/Plugin/NewMediaGalleryOpenDialogUrl.php @@ -5,15 +5,15 @@ */ declare(strict_types=1); -namespace Magento\MediaGalleryIntegration\Model; +namespace Magento\MediaGalleryIntegration\Plugin; -use Magento\Framework\DataObject; use Magento\MediaGalleryUiApi\Api\ConfigInterface; +use Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl; /** - * Provider to get open media gallery dialog URL for WYSIWYG and widgets + * Plugin to get open media gallery dialog URL for WYSIWYG and widgets */ -class OpenDialogUrlProvider extends DataObject +class NewMediaGalleryOpenDialogUrl { /** * @var ConfigInterface @@ -31,10 +31,13 @@ public function __construct(ConfigInterface $config) /** * Get Url based on media gallery configuration * + * @param OpenDialogUrl $subject + * @param string $result + * @SuppressWarnings(PHPMD.UnusedFormalParameter) * @return string */ - public function getUrl(): string + public function afterGet(OpenDialogUrl $subject, string $result) { - return $this->config->isEnabled() ? 'media_gallery/index/index' : 'cms/wysiwyg_images/index'; + return $this->config->isEnabled() ? 'media_gallery/index/index' : $result; } } diff --git a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlTest.php similarity index 80% rename from app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php rename to app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlTest.php index 7a3316f293879..90f363d6d792b 100644 --- a/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlProviderTest.php +++ b/app/code/Magento/MediaGalleryIntegration/Test/Integration/Model/OpenDialogUrlTest.php @@ -9,16 +9,16 @@ namespace Magento\MediaGalleryIntegration\Test\Integration\Model; use Magento\Framework\ObjectManagerInterface; -use Magento\MediaGalleryIntegration\Model\OpenDialogUrlProvider; use Magento\MediaGalleryUiApi\Api\ConfigInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl; use PHPUnit\Framework\TestCase; /** * Provide tests cover getting correct url based on the config settings. * @magentoAppArea adminhtml */ -class OpenDialogUrlProviderTest extends TestCase +class OpenDialogUrlTest extends TestCase { /** * @var ObjectManagerInterface @@ -26,9 +26,9 @@ class OpenDialogUrlProviderTest extends TestCase private $objectManger; /** - * @var OpenDialogUrlProvider + * @var OpenDialogUrl */ - private $openDialogUrlProvider; + private $openDialogUrl; /** * @inheritdoc @@ -37,8 +37,8 @@ protected function setUp(): void { $this->objectManger = Bootstrap::getObjectManager(); $config = $this->objectManger->create(ConfigInterface::class); - $this->openDialogUrlProvider = $this->objectManger->create( - OpenDialogUrlProvider::class, + $this->openDialogUrl = $this->objectManger->create( + OpenDialogUrl::class, ['config' => $config] ); } @@ -49,7 +49,7 @@ protected function setUp(): void */ public function testWithEnhancedMediaGalleryDisabled(): void { - self::assertEquals('cms/wysiwyg_images/index', $this->openDialogUrlProvider->getUrl()); + self::assertEquals('cms/wysiwyg_images/index', $this->openDialogUrl->get()); } /** @@ -58,6 +58,6 @@ public function testWithEnhancedMediaGalleryDisabled(): void */ public function testWithEnhancedMediaGalleryEnabled(): void { - self::assertEquals('media_gallery/index/index', $this->openDialogUrlProvider->getUrl()); + self::assertEquals('media_gallery/index/index', $this->openDialogUrl->get()); } } diff --git a/app/code/Magento/MediaGalleryIntegration/composer.json b/app/code/Magento/MediaGalleryIntegration/composer.json index c55d6e0b89733..a9709da81222e 100644 --- a/app/code/Magento/MediaGalleryIntegration/composer.json +++ b/app/code/Magento/MediaGalleryIntegration/composer.json @@ -6,7 +6,8 @@ "magento/framework": "*", "magento/module-media-gallery-ui-api": "*", "magento/module-media-gallery-api": "*", - "magento/module-media-gallery-synchronization-api": "*" + "magento/module-media-gallery-synchronization-api": "*", + "magento/module-ui": "*" }, "require-dev": { "magento/module-cms": "*" diff --git a/app/code/Magento/MediaGalleryIntegration/etc/adminhtml/di.xml b/app/code/Magento/MediaGalleryIntegration/etc/adminhtml/di.xml index 1559a6d7dfcd5..08e83ce6cad88 100644 --- a/app/code/Magento/MediaGalleryIntegration/etc/adminhtml/di.xml +++ b/app/code/Magento/MediaGalleryIntegration/etc/adminhtml/di.xml @@ -7,9 +7,7 @@ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Ui\Component\Form\Element\DataType\Media\OpenDialogUrl"> - <arguments> - <argument name="url" xsi:type="object">Magento\MediaGalleryIntegration\Model\OpenDialogUrlProvider</argument> - </arguments> + <plugin name="new_media_gallery_open_dialog_url" type="Magento\MediaGalleryIntegration\Plugin\NewMediaGalleryOpenDialogUrl" /> </type> <type name="Magento\Framework\File\Uploader"> <plugin name="save_asset_image" type="Magento\MediaGalleryIntegration\Plugin\SaveImageInformation"/> diff --git a/app/code/Magento/MediaGalleryMetadata/Model/File/ExtractMetadata.php b/app/code/Magento/MediaGalleryMetadata/Model/File/ExtractMetadata.php index 00f2b07f5bb81..f5efd25bca041 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/File/ExtractMetadata.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/File/ExtractMetadata.php @@ -7,7 +7,6 @@ namespace Magento\MediaGalleryMetadata\Model\File; -use Magento\Framework\Exception\LocalizedException; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; @@ -90,7 +89,12 @@ private function readSegments(FileInterface $file): MetadataInterface ); } - $data = $segmentReader->execute($file); + try { + $data = $segmentReader->execute($file); + } catch (\Exception $exception) { + continue; + } + $title = !empty($data->getTitle()) ? $data->getTitle() : $title; $description = !empty($data->getDescription()) ? $data->getDescription() : $description; diff --git a/app/code/Magento/MediaGalleryMetadata/Model/GetIptcMetadata.php b/app/code/Magento/MediaGalleryMetadata/Model/GetIptcMetadata.php index d7290f31ee34e..e100a7f852e42 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/GetIptcMetadata.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/GetIptcMetadata.php @@ -9,7 +9,6 @@ use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; -use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; /** * Get metadata from IPTC block @@ -42,8 +41,8 @@ public function __construct( */ public function execute(string $data): MetadataInterface { - $title = ''; - $description = ''; + $title = null; + $description = null; $keywords = []; if (is_callable('iptcparse')) { @@ -65,7 +64,7 @@ public function execute(string $data): MetadataInterface return $this->metadataFactory->create([ 'title' => $title, 'description' => $description, - 'keywords' => $keywords + 'keywords' => !empty($keywords) ? $keywords : null ]); } } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php new file mode 100644 index 0000000000000..b6c32296f3f7d --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadExif.php @@ -0,0 +1,104 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryMetadata\Model\Jpeg\Segment; + +use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; +use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; +use Magento\MediaGalleryMetadataApi\Model\FileInterface; +use Magento\MediaGalleryMetadataApi\Model\ReadMetadataInterface; +use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; +use Magento\Framework\Exception\LocalizedException; + +/** + * Jpeg EXIF Reader + */ +class ReadExif implements ReadMetadataInterface +{ + private const EXIF_SEGMENT_NAME = 'APP1'; + private const EXIF_SEGMENT_START = "Exif\x00"; + private const EXIF_DATA_START_POSITION = 0; + + /** + * @var MetadataInterfaceFactory + */ + private $metadataFactory; + + /** + * @param MetadataInterfaceFactory $metadataFactory + */ + public function __construct( + MetadataInterfaceFactory $metadataFactory + ) { + $this->metadataFactory = $metadataFactory; + } + + /** + * @inheritdoc + */ + public function execute(FileInterface $file): MetadataInterface + { + if (!is_callable('exif_read_data')) { + throw new LocalizedException( + __('exif_read_data() must be enabled in php configuration') + ); + } + + foreach ($file->getSegments() as $segment) { + if ($this->isExifSegment($segment)) { + return $this->getExifData($file->getPath()); + } + } + + return $this->metadataFactory->create([ + 'title' => null, + 'description' => null, + 'keywords' => null + ]); + } + + /** + * Parese exif data from segment + * + * @param string $filePath + */ + private function getExifData(string $filePath): MetadataInterface + { + $title = null; + $description = null; + $keywords = null; + + $data = exif_read_data($filePath); + + if (!empty($data)) { + $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; + $description = isset($data['ImageDescription']) ? $data['ImageDescription'] : null; + } + + return $this->metadataFactory->create([ + 'title' => $title, + 'description' => $description, + 'keywords' => $keywords + ]); + } + + /** + * Does segment contain Exif data + * + * @param SegmentInterface $segment + * @return bool + */ + private function isExifSegment(SegmentInterface $segment): bool + { + return $segment->getName() === self::EXIF_SEGMENT_NAME + && strncmp( + substr($segment->getData(), self::EXIF_DATA_START_POSITION, 5), + self::EXIF_SEGMENT_START, + 5 + ) == 0; + } +} diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadIptc.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadIptc.php index 94ccb400e5e0a..e56993528a041 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadIptc.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadIptc.php @@ -56,9 +56,9 @@ public function execute(FileInterface $file): MetadataInterface } } return $this->metadataFactory->create([ - 'title' => '', - 'description' => '', - 'keywords' => [] + 'title' => null, + 'description' => null, + 'keywords' => null ]); } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadXmp.php b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadXmp.php index 81ff7200c3475..e68c86d35eb97 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadXmp.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Jpeg/Segment/ReadXmp.php @@ -54,9 +54,9 @@ public function execute(FileInterface $file): MetadataInterface } } return $this->metadataFactory->create([ - 'title' => '', - 'description' => '', - 'keywords' => [] + 'title' => null, + 'description' => null, + 'keywords' => null ]); } diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php new file mode 100644 index 0000000000000..09aeaf526443a --- /dev/null +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadExif.php @@ -0,0 +1,97 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryMetadata\Model\Png\Segment; + +use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterface; +use Magento\MediaGalleryMetadataApi\Api\Data\MetadataInterfaceFactory; +use Magento\MediaGalleryMetadataApi\Model\FileInterface; +use Magento\MediaGalleryMetadataApi\Model\ReadMetadataInterface; +use Magento\MediaGalleryMetadataApi\Model\SegmentInterface; +use Magento\Framework\Exception\LocalizedException; + +/** + * Jpeg EXIF Reader + */ +class ReadExif implements ReadMetadataInterface +{ + private const EXIF_SEGMENT_NAME = 'eXIf'; + + /** + * @var MetadataInterfaceFactory + */ + private $metadataFactory; + + /** + * @param MetadataInterfaceFactory $metadataFactory + */ + public function __construct( + MetadataInterfaceFactory $metadataFactory + ) { + $this->metadataFactory = $metadataFactory; + } + + /** + * @inheritdoc + */ + public function execute(FileInterface $file): MetadataInterface + { + if (!is_callable('exif_read_data')) { + throw new LocalizedException( + __('exif_read_data() must be enabled in php configuration') + ); + } + + foreach ($file->getSegments() as $segment) { + if ($this->isExifSegment($segment)) { + return $this->getExifData($segment); + } + } + + return $this->metadataFactory->create([ + 'title' => null, + 'description' => null, + 'keywords' => null + ]); + } + + /** + * Parese exif data from segment + * + * @param SegmentInterface $segment + */ + private function getExifData(SegmentInterface $segment): MetadataInterface + { + $title = null; + $description = null; + $keywords = []; + + $data = exif_read_data('data://image/jpeg;base64,' . base64_encode($segment->getData())); + + if ($data) { + $title = isset($data['DocumentName']) ? $data['DocumentName'] : null; + $description = isset($data['ImageDescription']) ? $data['ImageDescription'] : null; + } + + return $this->metadataFactory->create([ + 'title' => $title, + 'description' => $description, + 'keywords' => !empty($keywords) ? $keywords : null + ]); + } + + /** + * Does segment contain Exif data + * + * @param SegmentInterface $segment + * @return bool + */ + private function isExifSegment(SegmentInterface $segment): bool + { + return strcmp($segment->getName(), self::EXIF_SEGMENT_NAME) === 0; + } +} diff --git a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadXmp.php b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadXmp.php index 83ba554f7bf5d..518697d421474 100644 --- a/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadXmp.php +++ b/app/code/Magento/MediaGalleryMetadata/Model/Png/Segment/ReadXmp.php @@ -55,9 +55,9 @@ public function execute(FileInterface $file): MetadataInterface } } return $this->metadataFactory->create([ - 'title' => '', - 'description' => '', - 'keywords' => [] + 'title' => null, + 'description' => null, + 'keywords' => null ]); } diff --git a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php index 982ccbb20fe2c..ebe96183eb1f2 100644 --- a/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php +++ b/app/code/Magento/MediaGalleryMetadata/Test/Integration/Model/ExtractMetadataTest.php @@ -37,14 +37,14 @@ protected function setUp(): void * @param string $fileName * @param string $title * @param string $description - * @param array $keywords + * @param null|array $keywords * @throws LocalizedException */ public function testExecute( string $fileName, string $title, string $description, - array $keywords + ?array $keywords ): void { $path = realpath(__DIR__ . '/../../_files/' . $fileName); $metadata = $this->extractMetadata->execute($path); @@ -62,6 +62,18 @@ public function testExecute( public function filesProvider(): array { return [ + [ + 'exif_image.png', + 'Exif title png imge', + 'Exif description png imge', + null + ], + [ + 'exif-image.jpeg', + 'Exif Magento title', + 'Exif description metadata', + null + ], [ 'macos-photos.jpeg', 'Title of the magento image', diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg index 144a56dac2d3e..1a345c2d33fdd 100644 Binary files a/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg and b/app/code/Magento/MediaGalleryMetadata/Test/_files/empty_iptc.jpeg differ diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg new file mode 100644 index 0000000000000..cfe27433fd9fc Binary files /dev/null and b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif-image.jpeg differ diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/exif_image.png b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif_image.png new file mode 100644 index 0000000000000..4a6bf30c2d516 Binary files /dev/null and b/app/code/Magento/MediaGalleryMetadata/Test/_files/exif_image.png differ diff --git a/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png b/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png index 966520f0d0112..95eb45f69b3ea 100644 Binary files a/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png and b/app/code/Magento/MediaGalleryMetadata/Test/_files/macos-preview.png differ diff --git a/app/code/Magento/MediaGalleryMetadata/etc/di.xml b/app/code/Magento/MediaGalleryMetadata/etc/di.xml index d2f1f90510488..4cd9a34e43a93 100644 --- a/app/code/Magento/MediaGalleryMetadata/etc/di.xml +++ b/app/code/Magento/MediaGalleryMetadata/etc/di.xml @@ -112,6 +112,7 @@ <argument name="segmentReaders" xsi:type="array"> <item name="xmp" xsi:type="object">Magento\MediaGalleryMetadata\Model\Png\Segment\ReadXmp</item> <item name="iptc" xsi:type="object">Magento\MediaGalleryMetadata\Model\Png\Segment\ReadIptc</item> + <item name="exif" xsi:type="object">Magento\MediaGalleryMetadata\Model\Png\Segment\ReadExif</item> </argument> </arguments> </virtualType> @@ -121,6 +122,7 @@ <argument name="segmentReaders" xsi:type="array"> <item name="xmp" xsi:type="object">Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadXmp</item> <item name="iptc" xsi:type="object">Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadIptc</item> + <item name="exif" xsi:type="object">Magento\MediaGalleryMetadata\Model\Jpeg\Segment\ReadExif</item> </argument> </arguments> </virtualType> diff --git a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php index 87d477507b680..b4c360c3e0538 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/CreateAssetFromFile.php @@ -12,23 +12,16 @@ use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\ReadInterface; use Magento\Framework\Filesystem\Driver\File; -use Magento\Framework\Stdlib\DateTime\TimezoneInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; -use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; -use Magento\MediaGallerySynchronization\Model\Filesystem\SplFileInfoFactory; -use Magento\MediaGallerySynchronization\Model\GetContentHash; +use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo; +use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface; /** * Create media asset object based on the file information */ -class CreateAssetFromFile +class CreateAssetFromFile implements CreateAssetFromFileInterface { - /** - * Date format - */ - private const DATE_FORMAT = 'Y-m-d H:i:s'; - /** * @var Filesystem */ @@ -39,11 +32,6 @@ class CreateAssetFromFile */ private $driver; - /** - * @var TimezoneInterface; - */ - private $date; - /** * @var AssetInterfaceFactory */ @@ -55,65 +43,45 @@ class CreateAssetFromFile private $getContentHash; /** - * @var ExtractMetadataInterface + * @var GetFileInfo */ - private $extractMetadata; - - /** - * @var SplFileInfoFactory - */ - private $splFileInfoFactory; + private $getFileInfo; /** * @param Filesystem $filesystem * @param File $driver - * @param TimezoneInterface $date * @param AssetInterfaceFactory $assetFactory * @param GetContentHash $getContentHash - * @param ExtractMetadataInterface $extractMetadata - * @param SplFileInfoFactory $splFileInfoFactory + * @param GetFileInfo $getFileInfo */ public function __construct( Filesystem $filesystem, File $driver, - TimezoneInterface $date, AssetInterfaceFactory $assetFactory, GetContentHash $getContentHash, - ExtractMetadataInterface $extractMetadata, - SplFileInfoFactory $splFileInfoFactory + GetFileInfo $getFileInfo ) { $this->filesystem = $filesystem; $this->driver = $driver; - $this->date = $date; $this->assetFactory = $assetFactory; $this->getContentHash = $getContentHash; - $this->extractMetadata = $extractMetadata; - $this->splFileInfoFactory = $splFileInfoFactory; + $this->getFileInfo = $getFileInfo; } /** - * Create and format media asset object - * - * @param string $path - * @return AssetInterface - * @throws FileSystemException + * @inheritdoc */ public function execute(string $path): AssetInterface { $absolutePath = $this->getMediaDirectory()->getAbsolutePath($path); - $file = $this->splFileInfoFactory->create($absolutePath); + $file = $this->getFileInfo->execute($absolutePath); [$width, $height] = getimagesize($absolutePath); - $metadata = $this->extractMetadata->execute($absolutePath); - return $this->assetFactory->create( [ 'id' => null, 'path' => $path, - 'title' => $metadata->getTitle() ?: $file->getBasename('.' . $file->getExtension()), - 'description' => $metadata->getDescription(), - 'createdAt' => $this->date->date($file->getCTime())->format(self::DATE_FORMAT), - 'updatedAt' => $this->date->date($file->getMTime())->format(self::DATE_FORMAT), + 'title' => $file->getBasename(), 'width' => $width, 'height' => $height, 'hash' => $this->getHash($path), diff --git a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php new file mode 100644 index 0000000000000..5e523fd0e905a --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/FileInfo.php @@ -0,0 +1,148 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronization\Model\Filesystem; + +/** + * Class for getting image file information. + */ +class FileInfo +{ + /** + * @var string + */ + private $path; + + /** + * @var string + */ + private $filename; + + /** + * @var string + */ + private $extension; + + /** + * @var $basename + */ + private $basename; + + /** + * @var int + */ + private $size; + + /** + * @var int + */ + private $mTime; + + /** + * @var int + */ + private $cTime; + + /** + * FileInfo constructor. + * + * @param string $path + * @param string $filename + * @param string $extension + * @param string $basename + * @param int $size + * @param int $mTime + * @param int $cTime + */ + public function __construct( + string $path, + string $filename, + string $extension, + string $basename, + int $size, + int $mTime, + int $cTime + ) { + $this->path = $path; + $this->filename = $filename; + $this->extension = $extension; + $this->basename = $basename; + $this->size = $size; + $this->mTime = $mTime; + $this->cTime = $cTime; + } + + /** + * Get path without filename. + * + * @return string + */ + public function getPath(): string + { + return $this->path; + } + + /** + * Get filename. + * + * @return string + */ + public function getFilename(): string + { + return $this->filename; + } + + /** + * Get file extension. + * + * @return string + */ + public function getExtension(): string + { + return $this->extension; + } + + /** + * Get file basename. + * + * @return string + */ + public function getBasename(): string + { + return $this->basename; + } + + /** + * Get file size. + * + * @return int + */ + public function getSize(): int + { + return $this->size; + } + + /** + * Get last modified time. + * + * @return int + */ + public function getMTime(): int + { + return $this->mTime; + } + + /** + * Get inode change time. + * + * @return int + */ + public function getCTime(): int + { + return $this->cTime; + } +} diff --git a/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php new file mode 100644 index 0000000000000..8f9080767d6e3 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Model/Filesystem/GetFileInfo.php @@ -0,0 +1,52 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronization\Model\Filesystem; + +use Magento\MediaGallerySynchronization\Model\Filesystem\FileInfoFactory; + +/** + * Get file information + */ +class GetFileInfo +{ + /** + * @var FileInfoFactory + */ + private $fileInfoFactory; + + /** + * GetFileInfo constructor. + * @param FileInfoFactory $fileInfoFactory + */ + public function __construct( + FileInfoFactory $fileInfoFactory + ) { + $this->fileInfoFactory = $fileInfoFactory; + } + + /** + * Get file information based on path provided. + * + * @param string $path + * @return FileInfo + */ + public function execute(string $path): FileInfo + { + $splFileInfo = new \SplFileInfo($path); + + return $this->fileInfoFactory->create([ + 'path' => $splFileInfo->getPath(), + 'filename' => $splFileInfo->getFilename(), + 'extension' => $splFileInfo->getExtension(), + 'basename' => $splFileInfo->getBasename('.' . $splFileInfo->getExtension()), + 'size' => $splFileInfo->getSize(), + 'mTime' => $splFileInfo->getMTime(), + 'cTime' => $splFileInfo->getCTime() + ]); + } +} diff --git a/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php b/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php index 5e825d57c5ce7..be672666786dd 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/GetAssetFromPath.php @@ -12,7 +12,7 @@ use Magento\MediaGalleryApi\Api\Data\AssetInterface; use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; -use Magento\MediaGallerySynchronization\Model\Filesystem\SplFileInfoFactory; +use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface; /** * Create media asset object based on the file information @@ -30,31 +30,23 @@ class GetAssetFromPath private $assetFactory; /** - * @var CreateAssetFromFile + * @var CreateAssetFromFileInterface */ private $createAssetFromFile; - /** - * @var SplFileInfoFactory - */ - private $splFileInfoFactory; - /** * @param AssetInterfaceFactory $assetFactory * @param GetAssetsByPathsInterface $getMediaGalleryAssetByPath - * @param CreateAssetFromFile $createAssetFromFile - * @param SplFileInfoFactory $splFileInfoFactory + * @param CreateAssetFromFileInterface $createAssetFromFile */ public function __construct( AssetInterfaceFactory $assetFactory, GetAssetsByPathsInterface $getMediaGalleryAssetByPath, - CreateAssetFromFile $createAssetFromFile, - SplFileInfoFactory $splFileInfoFactory + CreateAssetFromFileInterface $createAssetFromFile ) { $this->assetFactory = $assetFactory; $this->getAssetsByPaths = $getMediaGalleryAssetByPath; $this->createAssetFromFile = $createAssetFromFile; - $this->splFileInfoFactory= $splFileInfoFactory; } /** diff --git a/app/code/Magento/MediaGallerySynchronization/Model/SynchronizeFiles.php b/app/code/Magento/MediaGallerySynchronization/Model/SynchronizeFiles.php index 81e9629f703f3..eebb172e48202 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/SynchronizeFiles.php +++ b/app/code/Magento/MediaGallerySynchronization/Model/SynchronizeFiles.php @@ -16,7 +16,7 @@ use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; use Magento\MediaGallerySynchronizationApi\Model\ImportFilesInterface; use Magento\MediaGallerySynchronizationApi\Api\SynchronizeFilesInterface; -use Magento\MediaGallerySynchronization\Model\Filesystem\SplFileInfoFactory; +use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo; use Psr\Log\LoggerInterface; /** @@ -50,9 +50,9 @@ class SynchronizeFiles implements SynchronizeFilesInterface private $driver; /** - * @var SplFileInfoFactory + * @var GetFileInfo */ - private $splFileInfoFactory; + private $getFileInfo; /** * @var ImportFilesInterface @@ -69,7 +69,7 @@ class SynchronizeFiles implements SynchronizeFilesInterface * @param Filesystem $filesystem * @param DateTime $date * @param LoggerInterface $log - * @param SplFileInfoFactory $splFileInfoFactory + * @param GetFileInfo $getFileInfo * @param GetAssetsByPathsInterface $getAssetsByPaths * @param ImportFilesInterface $importFiles */ @@ -78,7 +78,7 @@ public function __construct( Filesystem $filesystem, DateTime $date, LoggerInterface $log, - SplFileInfoFactory $splFileInfoFactory, + GetFileInfo $getFileInfo, GetAssetsByPathsInterface $getAssetsByPaths, ImportFilesInterface $importFiles ) { @@ -86,7 +86,7 @@ public function __construct( $this->filesystem = $filesystem; $this->date = $date; $this->log = $log; - $this->splFileInfoFactory = $splFileInfoFactory; + $this->getFileInfo = $getFileInfo; $this->getAssetsByPaths = $getAssetsByPaths; $this->importFiles = $importFiles; } @@ -150,7 +150,7 @@ private function getFileModificationTime(string $path): string { return $this->date->gmtDate( self::DATE_FORMAT, - $this->splFileInfoFactory->create($this->getMediaDirectory()->getAbsolutePath($path))->getMTime() + $this->getFileInfo->execute($this->getMediaDirectory()->getAbsolutePath($path))->getMTime() ); } diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php new file mode 100644 index 0000000000000..6b1e8a676d02b --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/Filesystem/GetFileInfoTest.php @@ -0,0 +1,89 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronization\Test\Integration\Model\Filesystem; + +use Magento\MediaGallerySynchronization\Model\Filesystem\GetFileInfo; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Integration test for GetFileInfo + */ +class GetFileInfoTest extends TestCase +{ + /** + * @var GetFileInfo + */ + private $getFileInfo; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->getFileInfo = Bootstrap::getObjectManager()->get(GetFileInfo::class); + } + + /** + * @dataProvider filesProvider + * @param string $file + */ + public function testExecute( + string $file + ): void { + + $path = $this->getImageFilePath($file); + + $fileInfo = $this->getFileInfo->execute($path); + $expectedResult = new \SplFileInfo($path); + $this->assertEquals($expectedResult->getPath(), $fileInfo->getPath()); + $this->assertEquals($expectedResult->getFilename(), $fileInfo->getFilename()); + $this->assertEquals($expectedResult->getExtension(), $fileInfo->getExtension()); + $this->assertEquals( + $expectedResult->getBasename('.' . $expectedResult->getExtension()), + $fileInfo->getBasename() + ); + $this->assertEquals($expectedResult->getSize(), $fileInfo->getSize()); + $this->assertEquals($expectedResult->getMTime(), $fileInfo->getMTime()); + $this->assertEquals($expectedResult->getCTime(), $fileInfo->getCTime()); + } + + /** + * Data provider for testExecute + * + * @return array[] + */ + public function filesProvider(): array + { + return [ + [ + 'magento.jpg', + 'magento_2.jpg' + ] + ]; + } + + /** + * Return image file path + * + * @param string $filename + * @return string + */ + private function getImageFilePath(string $filename): string + { + return dirname(__DIR__, 2) + . DIRECTORY_SEPARATOR + . implode( + DIRECTORY_SEPARATOR, + [ + '_files', + $filename + ] + ); + } +} diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php index 6c4338c0935dc..8a44307298065 100644 --- a/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php +++ b/app/code/Magento/MediaGallerySynchronization/Test/Integration/Model/SynchronizeFilesTest.php @@ -9,14 +9,12 @@ use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Filesystem\DriverInterface; -use Magento\MediaGalleryApi\Api\Data\AssetInterface; -use Magento\MediaGalleryApi\Api\Data\KeywordInterface; use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; use Magento\MediaGallerySynchronizationApi\Api\SynchronizeFilesInterface; -use Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface; use Magento\TestFramework\Helper\Bootstrap; use PHPUnit\Framework\TestCase; @@ -45,11 +43,6 @@ class SynchronizeFilesTest extends TestCase */ private $mediaDirectory; - /** - * @var GetAssetsKeywordsInterface - */ - private $getAssetKeywords; - /** * @inheritdoc */ @@ -58,7 +51,6 @@ protected function setUp(): void $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); $this->synchronizeFiles = Bootstrap::getObjectManager()->get(SynchronizeFilesInterface::class); $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); - $this->getAssetKeywords = Bootstrap::getObjectManager()->get(GetAssetsKeywordsInterface::class); $this->mediaDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) ->getDirectoryWrite(DirectoryList::MEDIA); } @@ -67,18 +59,16 @@ protected function setUp(): void * Test for SynchronizeFiles::execute * * @dataProvider filesProvider - * @param null|string $file - * @param null|string $title - * @param null|string $description - * @param null|array $keywords + * @param string $file + * @param string $title + * @param string $source * @throws FileSystemException - * @throws \Magento\Framework\Exception\LocalizedException + * @throws LocalizedException */ public function testExecute( - ?string $file, - ?string $title, - ?string $description, - ?array $keywords + string $file, + string $title, + string $source ): void { $path = realpath(__DIR__ . '/../_files/' . $file); $modifiableFilePath = $this->mediaDirectory->getAbsolutePath($file); @@ -89,12 +79,10 @@ public function testExecute( $this->synchronizeFiles->execute([$file]); - $loadedAssets = $this->getAssetsByPath->execute([$file])[0]; - $loadedKeywords = $this->getKeywords($loadedAssets) ?: null; + $loadedAsset = $this->getAssetsByPath->execute([$file])[0]; - $this->assertEquals($title, $loadedAssets->getTitle()); - $this->assertEquals($description, $loadedAssets->getDescription()); - $this->assertEquals($keywords, $loadedKeywords); + $this->assertEquals($title, $loadedAsset->getTitle()); + $this->assertEquals($source, $loadedAsset->getSource()); $this->driver->deleteFile($modifiableFilePath); } @@ -110,42 +98,8 @@ public function filesProvider(): array [ '/magento.jpg', 'magento', - null, - null - ], - [ - '/magento_metadata.jpg', - 'Title of the magento image', - 'Description of the magento image', - [ - 'magento', - 'mediagallerymetadata' - ] + 'Local' ] ]; } - - /** - * Key asset keywords - * - * @param AssetInterface $asset - * @return string[] - */ - private function getKeywords(AssetInterface $asset): array - { - $assetKeywords = $this->getAssetKeywords->execute([$asset->getId()]); - - if (empty($assetKeywords)) { - return []; - } - - $keywords = current($assetKeywords)->getKeywords(); - - return array_map( - function (KeywordInterface $keyword) { - return $keyword->getKeyword(); - }, - $keywords - ); - } } diff --git a/app/code/Magento/MediaGallerySynchronization/composer.json b/app/code/Magento/MediaGallerySynchronization/composer.json index e1d4962366978..f9d642dd02568 100644 --- a/app/code/Magento/MediaGallerySynchronization/composer.json +++ b/app/code/Magento/MediaGallerySynchronization/composer.json @@ -6,8 +6,7 @@ "magento/framework": "*", "magento/module-media-gallery-api": "*", "magento/module-media-gallery-synchronization-api": "*", - "magento/framework-message-queue": "*", - "magento/module-media-gallery-metadata-api": "*" + "magento/framework-message-queue": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/MediaGallerySynchronization/etc/di.xml b/app/code/Magento/MediaGallerySynchronization/etc/di.xml index 47a4360575b2e..82bd1303eda74 100644 --- a/app/code/Magento/MediaGallerySynchronization/etc/di.xml +++ b/app/code/Magento/MediaGallerySynchronization/etc/di.xml @@ -9,11 +9,11 @@ <preference for="Magento\MediaGallerySynchronizationApi\Api\SynchronizeInterface" type="Magento\MediaGallerySynchronization\Model\Synchronize"/> <preference for="Magento\MediaGallerySynchronizationApi\Model\FetchBatchesInterface" type="Magento\MediaGallerySynchronization\Model\FetchBatches"/> <preference for="Magento\MediaGallerySynchronizationApi\Api\SynchronizeFilesInterface" type="Magento\MediaGallerySynchronization\Model\SynchronizeFiles"/> + <preference for="Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface" type="Magento\MediaGallerySynchronization\Model\CreateAssetFromFile"/> <type name="Magento\MediaGallerySynchronizationApi\Model\ImportFilesComposite"> <arguments> <argument name="importers" xsi:type="array"> - <item name="0" xsi:type="object">Magento\MediaGallerySynchronization\Model\ImportMediaAsset</item> - <item name="1" xsi:type="object">Magento\MediaGallerySynchronization\Model\ImportImageFileKeywords</item> + <item name="10" xsi:type="object">Magento\MediaGallerySynchronization\Model\ImportMediaAsset</item> </argument> </arguments> </type> diff --git a/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFileInterface.php b/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFileInterface.php new file mode 100644 index 0000000000000..667c2e68a27d8 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationApi/Model/CreateAssetFromFileInterface.php @@ -0,0 +1,26 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronizationApi\Model; + +use Magento\Framework\Exception\FileSystemException; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; + +/** + * Create media asset object from the media file + */ +interface CreateAssetFromFileInterface +{ + /** + * Create media asset object from the media file + * + * @param string $path + * @return AssetInterface + * @throws FileSystemException + */ + public function execute(string $path): AssetInterface; +} diff --git a/app/code/Magento/MediaGallerySynchronizationApi/composer.json b/app/code/Magento/MediaGallerySynchronizationApi/composer.json index 427bd2bd4aca7..19bab75dd5f42 100644 --- a/app/code/Magento/MediaGallerySynchronizationApi/composer.json +++ b/app/code/Magento/MediaGallerySynchronizationApi/composer.json @@ -3,7 +3,8 @@ "description": "Magento module responsible for the media gallery synchronization implementation API", "require": { "php": "~7.3.0||~7.4.0", - "magento/framework": "*" + "magento/framework": "*", + "magento/module-media-gallery-api": "*" }, "type": "magento2-module", "license": [ diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE.txt b/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE.txt new file mode 100644 index 0000000000000..49525fd99da9c --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE.txt @@ -0,0 +1,48 @@ + +Open Software License ("OSL") v. 3.0 + +This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Open Software License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. \ No newline at end of file diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE_AFL.txt b/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE_AFL.txt new file mode 100644 index 0000000000000..f39d641b18a19 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/LICENSE_AFL.txt @@ -0,0 +1,48 @@ + +Academic Free License ("AFL") v. 3.0 + +This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work: + +Licensed under the Academic Free License version 3.0 + + 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following: + + 1. to reproduce the Original Work in copies, either alone or as part of a collective work; + + 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work; + + 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License; + + 4. to perform the Original Work publicly; and + + 5. to display the Original Work publicly. + + 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works. + + 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work. + + 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license. + + 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c). + + 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work. + + 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer. + + 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation. + + 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c). + + 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware. + + 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License. + + 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License. + + 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. + + 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. + + 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under <insert your license name here>" or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process. diff --git a/app/code/Magento/MediaGallerySynchronization/Model/ImportImageFileKeywords.php b/app/code/Magento/MediaGallerySynchronizationMetadata/Model/ImportKeywords.php similarity index 93% rename from app/code/Magento/MediaGallerySynchronization/Model/ImportImageFileKeywords.php rename to app/code/Magento/MediaGallerySynchronizationMetadata/Model/ImportKeywords.php index 361137ad27686..a9910157f27c7 100644 --- a/app/code/Magento/MediaGallerySynchronization/Model/ImportImageFileKeywords.php +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/Model/ImportKeywords.php @@ -5,12 +5,11 @@ */ declare(strict_types=1); -namespace Magento\MediaGallerySynchronization\Model; +namespace Magento\MediaGallerySynchronizationMetadata\Model; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\ReadInterface; -use Magento\Framework\Filesystem\Driver\File; use Magento\MediaGalleryApi\Api\Data\AssetKeywordsInterfaceFactory; use Magento\MediaGalleryApi\Api\Data\KeywordInterface; use Magento\MediaGalleryApi\Api\Data\KeywordInterfaceFactory; @@ -22,18 +21,13 @@ /** * import image keywords from file metadata */ -class ImportImageFileKeywords implements ImportFilesInterface +class ImportKeywords implements ImportFilesInterface { /** * @var Filesystem */ private $filesystem; - /** - * @var File - */ - private $driver; - /** * @var KeywordInterfaceFactory */ @@ -60,7 +54,6 @@ class ImportImageFileKeywords implements ImportFilesInterface private $getAssetsByPaths; /** - * @param File $driver * @param Filesystem $filesystem * @param KeywordInterfaceFactory $keywordFactory * @param ExtractMetadataInterface $extractMetadata @@ -69,7 +62,6 @@ class ImportImageFileKeywords implements ImportFilesInterface * @param GetAssetsByPathsInterface $getAssetsByPaths */ public function __construct( - File $driver, Filesystem $filesystem, KeywordInterfaceFactory $keywordFactory, ExtractMetadataInterface $extractMetadata, @@ -77,7 +69,6 @@ public function __construct( AssetKeywordsInterfaceFactory $assetKeywordsFactory, GetAssetsByPathsInterface $getAssetsByPaths ) { - $this->driver = $driver; $this->filesystem = $filesystem; $this->keywordFactory = $keywordFactory; $this->extractMetadata = $extractMetadata; @@ -123,6 +114,7 @@ private function getMetadataKeywords(string $path): ?array { $metadataKeywords = $this->extractMetadata->execute($this->getMediaDirectory()->getAbsolutePath($path)) ->getKeywords(); + if ($metadataKeywords === null) { return null; } diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php b/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php new file mode 100644 index 0000000000000..59604c0b3e501 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/Plugin/CreateAssetFromFileMetadata.php @@ -0,0 +1,81 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronizationMetadata\Plugin; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Filesystem; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Api\Data\AssetInterfaceFactory; +use Magento\MediaGalleryMetadataApi\Api\ExtractMetadataInterface; +use Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface; + +/** + * Add metadata to the asset created from file + */ +class CreateAssetFromFileMetadata +{ + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var AssetInterfaceFactory + */ + private $assetFactory; + + /** + * @var ExtractMetadataInterface + */ + private $extractMetadata; + + /** + * @param Filesystem $filesystem + * @param AssetInterfaceFactory $assetFactory + * @param ExtractMetadataInterface $extractMetadata + */ + public function __construct( + Filesystem $filesystem, + AssetInterfaceFactory $assetFactory, + ExtractMetadataInterface $extractMetadata + ) { + $this->filesystem = $filesystem; + $this->assetFactory = $assetFactory; + $this->extractMetadata = $extractMetadata; + } + + /** + * Add metadata to the asset + * + * @param CreateAssetFromFileInterface $subject + * @param AssetInterface $asset + * @return AssetInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterExecute(CreateAssetFromFileInterface $subject, AssetInterface $asset): AssetInterface + { + $metadata = $this->extractMetadata->execute( + $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath($asset->getPath()) + ); + + return $this->assetFactory->create( + [ + 'id' => $asset->getId(), + 'path' => $asset->getPath(), + 'title' => $metadata->getTitle() ?: $asset->getTitle(), + 'description' => $metadata->getDescription(), + 'width' => $asset->getWidth(), + 'height' => $asset->getHeight(), + 'hash' => $asset->getHash(), + 'size' => $asset->getSize(), + 'contentType' => $asset->getContentType(), + 'source' => $asset->getSource() + ] + ); + } +} diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/README.md b/app/code/Magento/MediaGallerySynchronizationMetadata/README.md new file mode 100644 index 0000000000000..64988dd543fe4 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/README.md @@ -0,0 +1,3 @@ +# Magento_MediaGallerySynchronizationMetadata + +The purpose of this module is to include assets metadata to media gallery synchronization process diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json b/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json new file mode 100644 index 0000000000000..0674014026b24 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/composer.json @@ -0,0 +1,24 @@ +{ + "name": "magento/module-media-gallery-synchronization-metadata", + "description": "Magento module responsible for images metadata synchronization", + "require": { + "php": "~7.3.0||~7.4.0", + "magento/framework": "*", + "magento/module-media-gallery-api": "*", + "magento/module-media-gallery-metadata-api": "*", + "magento/module-media-gallery-synchronization-api": "*" + }, + "type": "magento2-module", + "license": [ + "OSL-3.0", + "AFL-3.0" + ], + "autoload": { + "files": [ + "registration.php" + ], + "psr-4": { + "Magento\\MediaGallerySynchronizationMetadata\\": "" + } + } +} diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml new file mode 100644 index 0000000000000..ed66fd08cabfc --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/di.xml @@ -0,0 +1,19 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\MediaGallerySynchronizationApi\Model\ImportFilesComposite"> + <arguments> + <argument name="importers" xsi:type="array"> + <item name="20" xsi:type="object">Magento\MediaGallerySynchronizationMetadata\Model\ImportKeywords</item> + </argument> + </arguments> + </type> + <type name="Magento\MediaGallerySynchronizationApi\Model\CreateAssetFromFileInterface"> + <plugin name="addMetadataToAssetCreatedFromFile" type="Magento\MediaGallerySynchronizationMetadata\Plugin\CreateAssetFromFileMetadata"/> + </type> +</config> diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/etc/module.xml b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/module.xml new file mode 100644 index 0000000000000..f92c370496d2d --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/etc/module.xml @@ -0,0 +1,10 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd"> + <module name="Magento_MediaGallerySynchronizationMetadata"/> +</config> diff --git a/app/code/Magento/MediaGallerySynchronizationMetadata/registration.php b/app/code/Magento/MediaGallerySynchronizationMetadata/registration.php new file mode 100644 index 0000000000000..82315db519f82 --- /dev/null +++ b/app/code/Magento/MediaGallerySynchronizationMetadata/registration.php @@ -0,0 +1,14 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +use Magento\Framework\Component\ComponentRegistrar; + +ComponentRegistrar::register( + ComponentRegistrar::MODULE, + 'Magento_MediaGallerySynchronizationMetadata', + __DIR__ +); diff --git a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php new file mode 100644 index 0000000000000..09837c301c367 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/GetSelected.php @@ -0,0 +1,99 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryUi\Controller\Adminhtml\Asset; + +use Magento\Backend\App\Action; +use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\Controller\Result\JsonFactory; +use Magento\Framework\Controller\ResultInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByIdsInterface; +use Magento\Cms\Helper\Wysiwyg\Images; +use Magento\Cms\Model\Wysiwyg\Images\Storage; + +/** + * Controller to get selected asset for ui-select component + */ +class GetSelected extends Action implements HttpGetActionInterface +{ + /** + * Authorization level of a basic admin session + * + * @see _isAllowed() + */ + const ADMIN_RESOURCE = 'Magento_Cms::media_gallery'; + + /** + * @var JsonFactory + */ + private $resultJsonFactory; + + /** + * @var GetAssetsByIdsInterface + */ + private $getAssetById; + + /** + * @var Images + */ + private $images; + + /** + * @var Storage + */ + private $storage; + + /** + * @param JsonFactory $resultFactory + * @param GetAssetsByIdsInterface $getAssetById + * @param Context $context + * @param Images $images + * @param Storage $storage + * + */ + public function __construct( + JsonFactory $resultFactory, + GetAssetsByIdsInterface $getAssetById, + Context $context, + Images $images, + Storage $storage + ) { + $this->resultJsonFactory = $resultFactory; + $this->getAssetById = $getAssetById; + $this->images = $images; + $this->storage = $storage; + parent::__construct($context); + } + + /** + * Return selected asset options. + * + * @return ResultInterface + */ + public function execute(): ResultInterface + { + $options = []; + $assetIds = $this->getRequest()->getParam('ids'); + + if (!is_array($assetIds)) { + return $this->resultJsonFactory->create()->setData('parameter ids must be type of array'); + } + $assets = $this->getAssetById->execute($assetIds); + + foreach ($assets as $asset) { + $assetPath = $this->storage->getThumbnailUrl($this->images->getStorageRoot() . $asset->getPath()); + $options[] = [ + 'value' => (string) $asset->getId(), + 'label' => $asset->getTitle(), + 'src' => $assetPath + ]; + } + + return $this->resultJsonFactory->create()->setData($options); + } +} diff --git a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/Search.php b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/Search.php index df13250eacb5f..9b6c08edbc86d 100644 --- a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/Search.php +++ b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Asset/Search.php @@ -139,7 +139,7 @@ public function execute() $responseContent['options'][] = [ 'value' => (string) $asset->getId(), 'label' => $asset->getTitle(), - 'path' => $this->storage->getThumbnailUrl($this->images->getStorageRoot() . $asset->getPath()) + 'src' => $this->storage->getThumbnailUrl($this->images->getStorageRoot() . $asset->getPath()) ]; $responseContent['total'] = count($responseContent['options']); } diff --git a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Media/Index.php b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Media/Index.php index 3660374243d16..8c5b3d4d3a9ac 100644 --- a/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Media/Index.php +++ b/app/code/Magento/MediaGalleryUi/Controller/Adminhtml/Media/Index.php @@ -12,6 +12,9 @@ use Magento\Framework\App\Action\HttpGetActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Controller\ResultInterface; +use Magento\MediaContentApi\Model\Config; +use Magento\Backend\App\Action\Context; +use Magento\Backend\Model\View\Result\Forward; /** * Controller serving the media gallery content @@ -20,6 +23,24 @@ class Index extends Action implements HttpGetActionInterface { public const ADMIN_RESOURCE = 'Magento_Cms::media_gallery'; + /** + * @var Config + */ + private $config; + + /** + * Index constructor. + * @param Context $context + * @param Config $config + */ + public function __construct( + Context $context, + Config $config + ) { + parent::__construct($context); + $this->config = $config; + } + /** * Get the media gallery layout * @@ -27,6 +48,14 @@ class Index extends Action implements HttpGetActionInterface */ public function execute(): ResultInterface { + if (!$this->config->isEnabled()) { + /** @var Forward $resultForward */ + $resultForward = $this->resultFactory->create(ResultFactory::TYPE_FORWARD); + $resultForward->forward('noroute'); + + return $resultForward; + } + /** @var Page $resultPage */ $resultPage = $this->resultFactory->create(ResultFactory::TYPE_PAGE); $resultPage->setActiveMenu('Magento_MediaGalleryUi::media_gallery') diff --git a/app/code/Magento/MediaGalleryUi/Model/Config/MediaGallery/Yesno.php b/app/code/Magento/MediaGalleryUi/Model/Config/MediaGallery/Yesno.php new file mode 100644 index 0000000000000..40cf7630d9911 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Model/Config/MediaGallery/Yesno.php @@ -0,0 +1,21 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGalleryUi\Model\Config\MediaGallery; + +class Yesno implements \Magento\Framework\Data\OptionSourceInterface +{ + /** + * Options getter + * + * @return array + */ + public function toOptionArray() :array + { + return [['value' => 0, 'label' => __('Yes')], ['value' => 1, 'label' => __('No')]]; + } +} diff --git a/app/code/Magento/MediaGalleryUi/Model/Directories/GetFolderTree.php b/app/code/Magento/MediaGalleryUi/Model/Directories/GetFolderTree.php index f0998a3e120f2..c22165ba4e51f 100644 --- a/app/code/Magento/MediaGalleryUi/Model/Directories/GetFolderTree.php +++ b/app/code/Magento/MediaGalleryUi/Model/Directories/GetFolderTree.php @@ -7,13 +7,14 @@ namespace Magento\MediaGalleryUi\Model\Directories; +use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\ValidatorException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\Read; use Magento\MediaGalleryApi\Api\IsPathExcludedInterface; /** - * Build folder tree structure by path + * Build media gallery folder tree structure by path */ class GetFolderTree { @@ -22,43 +23,45 @@ class GetFolderTree */ private $filesystem; - /** - * @var string - */ - private $path; - /** * @var IsPathExcludedInterface */ private $isPathExcluded; /** - * Constructor - * * @param Filesystem $filesystem - * @param string $path * @param IsPathExcludedInterface $isPathExcluded */ public function __construct( Filesystem $filesystem, - string $path, IsPathExcludedInterface $isPathExcluded ) { $this->filesystem = $filesystem; - $this->path = $path; $this->isPathExcluded = $isPathExcluded; } /** * Return directory folder structure in array * - * @param bool $skipRoot * @return array * @throws ValidatorException */ - public function execute(bool $skipRoot = true): array + public function execute(): array { - return $this->buildFolderTree($this->getDirectories(), $skipRoot); + $tree = [ + 'name' => 'root', + 'path' => '/', + 'children' => [] + ]; + $directories = $this->getDirectories(); + foreach ($directories as $idx => &$node) { + $node['children'] = []; + $result = $this->findParent($node, $tree); + $parent = &$result['treeNode']; + + $parent['children'][] = &$directories[$idx]; + } + return $tree['children']; } /** @@ -72,7 +75,7 @@ private function getDirectories(): array $directories = []; /** @var Read $directory */ - $directory = $this->filesystem->getDirectoryRead($this->path); + $directory = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA); if (!$directory->isDirectory()) { return $directories; @@ -96,30 +99,6 @@ private function getDirectories(): array return $directories; } - /** - * Build folder tree structure by provided directories path - * - * @param array $directories - * @param bool $skipRoot - * @return array - */ - private function buildFolderTree(array $directories, bool $skipRoot): array - { - $tree = [ - 'name' => 'root', - 'path' => '/', - 'children' => [] - ]; - foreach ($directories as $idx => &$node) { - $node['children'] = []; - $result = $this->findParent($node, $tree); - $parent = & $result['treeNode']; - - $parent['children'][] =& $directories[$idx]; - } - return $skipRoot ? $tree['children'] : $tree; - } - /** * Find parent directory * diff --git a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php index ff82b990d2a01..85522c6b07e00 100644 --- a/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php +++ b/app/code/Magento/MediaGalleryUi/Model/UpdateAsset.php @@ -76,18 +76,16 @@ public function execute(int $id, MetadataInterface $data): void $updatedAsset = $this->assetFactory->create( [ + 'id' => $asset->getId(), 'path' => $asset->getPath(), - 'contentType' => $asset->getContentType(), + 'title' => $data->getTitle() ?? $asset->getTitle(), + 'description' => $data->getDescription() ?? $asset->getDescription(), 'width' => $asset->getWidth(), 'height' => $asset->getHeight(), 'size' => $asset->getSize(), - 'id' => $asset->getId(), - 'title' => $data->getTitle() ?? $asset->getTitle(), - 'description' => $data->getDescription() ?? $asset->getDescription(), - 'source' => $asset->getSource(), 'hash' => $asset->getHash(), - 'created_at' => $asset->getCreatedAt(), - 'updated_at' => $asset->getUpdatedAt() + 'contentType' => $asset->getContentType(), + 'source' => $asset->getSource() ] ); diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceholderActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceholderActionGroup.xml new file mode 100644 index 0000000000000..db400ff151ae3 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminAssertMediaGalleryFilterPlaceholderActionGroup.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminAssertMediaGalleryFilterPlaceholderActionGroup"> + <annotations> + <description>Assert asset filter placeholder value</description> + </annotations> + <arguments> + <argument name="filterPlaceholder" type="string"/> + </arguments> + + <seeElement selector="{{AdminMediaGalleryCatalogUiCategoryGridSection.activeFilterPlaceholder(filterPlaceholder)}}" stepKey="assertFilterPLaceHolder" /> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryAssertImagesDeletedInBulkActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryAssertImagesDeletedInBulkActionGroup.xml index 7f4db971702ca..ca503b7357300 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryAssertImagesDeletedInBulkActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryAssertImagesDeletedInBulkActionGroup.xml @@ -12,7 +12,9 @@ <annotations> <description>Asserts images has been deleted in mass action.</description> </annotations> - - <see userInput='Assets have been successfully deleted' stepKey="verifyDeleteImages"/> + <arguments> + <argument name="numberOfAssetsDeleted" type="string"/> + </arguments> + <see userInput='{{numberOfAssetsDeleted}} assets have been successfully deleted.' stepKey="verifyDeleteImages"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryAssertMassActionModeNotActiveActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryAssertMassActionModeNotActiveActionGroup.xml index a691f65387e8e..1ec2004b22f24 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryAssertMassActionModeNotActiveActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryAssertMassActionModeNotActiveActionGroup.xml @@ -12,8 +12,8 @@ <annotations> <description>Asserts that massaction mode is terminated</description> </annotations> - + <dontSeeElement selector="{{AdminMediaGalleryHeaderButtonsSection.addSelected}}" stepKey="verifyAddSelectedButtonNotVisible"/> <dontSeeElement selector="{{AdminEnhancedMediaGalleryMassActionSection.totalSelected}}" stepKey="verifyTeminateMassAction"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryClickSortActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryClickSortActionGroup.xml new file mode 100644 index 0000000000000..7679da6585d5f --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminEnhancedMediaGalleryClickSortActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminEnhancedMediaGalleryClickSortActionGroup"> + <arguments> + <argument name="sortName" type="string"/> + </arguments> + <click selector="{{AdminEnhancedMediaGallerySortBySection.sortDropdown}}" stepKey="clickOnSortDropdown"/> + <click selector="{{AdminEnhancedMediaGallerySortBySection.sortOption(sortName)}}" stepKey="clickOnSortOption"/> + <waitForPageLoad stepKey="waitForLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertImageInGridActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertImageInGridActionGroup.xml index 6785558c8ef54..08c93a805dc70 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertImageInGridActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryAssertImageInGridActionGroup.xml @@ -16,6 +16,5 @@ <argument name="title"/> </arguments> <waitForElementVisible selector="{{AdminEnhancedMediaGalleryImageActionsSection.imageInGrid(title)}}" stepKey="waitForImageToBeVisible"/> - </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml new file mode 100644 index 0000000000000..b2ce726b3bd6c --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminMediaGalleryEditAssetRemoveKeywordActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminMediaGalleryEditAssetRemoveKeywordActionGroup"> + <annotations> + <description>Remove Keywords on the Edit Details panel</description> + </annotations> + <arguments> + <argument name="keyword" type="string"/> + </arguments> + + <click selector="{{AdminEnhancedMediaGalleryEditDetailsSection.removeSelectedKeyword(keyword)}}" stepKey="removeKeyword"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminOpenMediaGalleryFromCategoryImageUploaderActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminOpenMediaGalleryFromCategoryImageUploaderActionGroup.xml index 6f38bd7c7d738..89664ef152dba 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminOpenMediaGalleryFromCategoryImageUploaderActionGroup.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AdminOpenMediaGalleryFromCategoryImageUploaderActionGroup.xml @@ -15,6 +15,6 @@ <conditionalClick stepKey="clickExpandContent" selector="{{AdminCategoryContentSection.sectionHeader}}" dependentSelector="{{AdminCategoryContentSection.selectFromGalleryButton}}" visible="false" /> <waitForElementVisible selector="{{AdminCategoryContentSection.selectFromGalleryButton}}" stepKey="waitForSelectFromGallery" /> <click selector="{{AdminCategoryContentSection.selectFromGalleryButton}}" stepKey="clickSelectFromGallery" /> - <waitForPageLoad stepKey="waitForPageLoad" /> + <waitForPageLoad stepKey="waitForPageLoad"/> </actionGroup> </actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml new file mode 100644 index 0000000000000..9460d0b339ca4 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup"> + <annotations> + <description>Assert that created_at updated_at time NOT equals</description> + </annotations> + + <grabTextFrom selector="{{AdminEnhancedMediaGalleryViewDetailsSection.createdAtDate}}" stepKey="grabCreatedTime"/> + <grabTextFrom selector="{{AdminEnhancedMediaGalleryViewDetailsSection.updatedAtDate}}" stepKey="grabModifietTime"/> + <assertNotEquals stepKey="verifyContentType"> + <actualResult type="variable">grabCreatedTime</actualResult> + <expectedResult type="variable">grabModifietTime</expectedResult> + </assertNotEquals> + + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGallerySortByActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGallerySortByActionGroup.xml new file mode 100644 index 0000000000000..451ef81f0ff9f --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGallerySortByActionGroup.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminEnhancedMediaGallerySortByActionGroup"> + <annotations> + <description>Assert the images position in the grid after sorting has been applied.</description> + </annotations> + <arguments> + <argument name="firstImageFile" type="string"/> + <argument name="secondImageFile" type="string"/> + <argument name="thirdImageFile" type="string"/> + </arguments> + + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('0')}}" userInput="src" + stepKey="getFirstImageSrcAfterSort"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('1')}}" userInput="src" + stepKey="getSecondImageSrcAfterSort"/> + <grabAttributeFrom selector="{{AdminEnhancedMediaGalleryGridImagePositionSection.nthImageInGrid('2')}}" userInput="src" + stepKey="getThirdImageSrcAfterSort"/> + + <assertStringContainsString stepKey="assertFirstImagePositionAfterSort"> + <actualResult type="string">{$getFirstImageSrcAfterSort}</actualResult> + <expectedResult type="string">{{firstImageFile}}</expectedResult> + </assertStringContainsString> + <assertStringContainsString stepKey="assertSecondImagePositionAfterSort"> + <actualResult type="string">{$getSecondImageSrcAfterSort}</actualResult> + <expectedResult type="string">{{secondImageFile}}</expectedResult> + </assertStringContainsString> + <assertStringContainsString stepKey="assertThirdImagePositionAfterSort"> + <actualResult type="string">{$getThirdImageSrcAfterSort}</actualResult> + <expectedResult type="string">{{thirdImageFile}}</expectedResult> + </assertStringContainsString> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml new file mode 100644 index 0000000000000..076885ddaf8b6 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup"> + <annotations> + <description>Assert that created_at updated_at time are the same for newly uploaded image </description> + </annotations> + + <grabTextFrom selector="{{AdminEnhancedMediaGalleryViewDetailsSection.createdAtDate}}" stepKey="grabCreatedTime"/> + <grabTextFrom selector="{{AdminEnhancedMediaGalleryViewDetailsSection.updatedAtDate}}" stepKey="grabModifietTime"/> + <assertEquals stepKey="verifyContentType"> + <actualResult type="variable">grabCreatedTime</actualResult> + <expectedResult type="variable">grabModifietTime</expectedResult> + </assertEquals> + + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup.xml new file mode 100644 index 0000000000000..62adffc931c16 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertAdminEnhancedMediaGalleryUsedInSectionNotDisplayedActionGroup"> + <annotations> + <description>Assert that's used in section not displayed in view details.</description> + </annotations> + + <dontSeeElement selector="{{AdminEnhancedMediaGalleryViewDetailsSection.usedIn}}" stepKey="assertImageIsDeleted"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertFolderIsChangedActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertFolderIsChangedActionGroup.xml new file mode 100644 index 0000000000000..090dbed8b4f78 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssertFolderIsChangedActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssertFolderIsChangedActionGroup"> + <annotations> + <description>Assert that folder is changed</description> + </annotations> + <arguments> + <argument name="newSelectedFolder" type="string"/> + <argument name="oldSelectedFolder" type="string" defaultValue="{{AdminMediaGalleryFolderData.name}}"/> + </arguments> + + <assertNotEquals stepKey="assertNotEqual"> + <actualResult type="string">{{newSelectedFolder}}</actualResult> + <expectedResult type="string">{{oldSelectedFolder}}</expectedResult> + </assertNotEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml new file mode 100644 index 0000000000000..be9c7e939103d --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/ActionGroup/AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup"> + <annotations> + <description>Verifies that the passed comma-separated list of keywords are not present on the View Details panel</description> + </annotations> + <arguments> + <argument name="keywords" type="string"/> + </arguments> + + <grabTextFrom selector="{{AdminEnhancedMediaGalleryViewDetailsSection.keywords}}" stepKey="grabKeywords"/> + <assertStringNotContainsString stepKey="verifyKeywords"> + <actualResult type="variable">grabKeywords</actualResult> + <expectedResult type="string">{{keywords}}</expectedResult> + </assertStringNotContainsString> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml index b8e2f698ccfe8..b0bed4563003e 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryEditDetailsSection.xml @@ -14,6 +14,7 @@ <element name="description" type="textarea" selector="#description"/> <element name="newKeyword" type="input" selector="[data-ui-id='keyword']"/> <element name="addNewKeyword" type="input" selector="[data-ui-id='add-keyword']"/> + <element name="removeSelectedKeyword" type="button" selector="//span[contains(text(), '{{keyword}}')]/following-sibling::button[@data-action='remove-selected-item']" parameterized="true"/> <element name="cancel" type="button" selector="#image-details-action-cancel"/> <element name="save" type="button" selector="#image-details-action-save"/> </section> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryFiltersSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryFiltersSection.xml index 32b109f1e0483..da9f773d0f75e 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryFiltersSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryFiltersSection.xml @@ -25,5 +25,6 @@ <element name="searchOptionsFilterOption" type="text" selector="//div[label/span[contains(text(), '{{filterName}}')]]//label[@class='admin__action-multiselect-label']/span[text()='{{optionName}}']" parameterized="true" timeout="30"/> <element name="searchOptionsFilterDone" type="button" selector="//div[label/span[contains(text(), '{{filterName}}')]]//button[@data-action='close-advanced-select']" parameterized="true"/> <element name="duplicatedFilterCheckbox" type="button" selector="//input[@name='duplicated']"/> + <element name="activeFilterValue" type="text" selector="//div[@class='media-gallery-container']//div[@class='admin__current-filters-list-wrap']//li//span[contains(text(), '{{filterPlaceholder}}')]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryGridImagePositionSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryGridImagePositionSection.xml new file mode 100644 index 0000000000000..943f29d5fa851 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryGridImagePositionSection.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminEnhancedMediaGalleryGridImagePositionSection"> + <element name="nthImageInGrid" type="text" selector="div[class='masonry-image-column'][data-repeat-index='{{row}}'] img" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGallerySortBySection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGallerySortBySection.xml new file mode 100644 index 0000000000000..5ffcec00fcf4b --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGallerySortBySection.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<sections xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd"> + <section name="AdminEnhancedMediaGallerySortBySection"> + <element name="sortDropdown" type="button" selector="div[class='masonry-image-sortby'] select"/> + <element name="sortOption" type="button" selector="//div[@class='masonry-image-sortby'] //option[@value='{{sortOption}}']" parameterized="true"/> + </section> +</sections> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml index 048739ed3f81d..d6abe464048c7 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Section/AdminEnhancedMediaGalleryViewDetailsSection.xml @@ -18,6 +18,9 @@ <element name="edit" type="button" selector="//div[@class='media-gallery-image-details-modal']//button[contains(@class, 'edit')]"/> <element name="delete" type="button" selector="//div[@class='media-gallery-image-details-modal']//button[contains(@class, 'delete')]"/> <element name="confirmDelete" type="button" selector=".action-accept"/> + <element name="createdAtDate" type="button" selector="//div[@class='attribute']/span[contains(text(), 'Created')]/following-sibling::div"/> + <element name="usedIn" type="button" selector="//div[@class='attribute']/span[contains(text(), 'Used In')]"/> + <element name="updatedAtDate" type="button" selector="//div[@class='attribute']/span[contains(text(), 'Modified')]/following-sibling::div"/> <element name="addImage" type="button" selector=".add-image-action"/> <element name="cancel" type="button" selector="#image-details-action-cancel"/> <element name="usedInLink" type="button" parameterized="true" selector="//div[@class='attribute']/span[contains(text(), 'Used In')]/following-sibling::div/a[contains(text(), '{{entityName}}')]"/> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiDisabledSuite.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiDisabledSuite.xml new file mode 100644 index 0000000000000..727fbde3f17b6 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Suite/MediaGalleryUiDisabledSuite.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<suites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Suite/etc/suiteSchema.xsd"> + <suite name="MediaGalleryUiDisabledSuite"> + <include> + <group name="media_gallery_ui_disabled"/> + </include> + </suite> +</suites> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryDeleteImagesInBulkTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryDeleteImagesInBulkTest.xml index 94831b039b53a..fe2b5b1639fbe 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryDeleteImagesInBulkTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryDeleteImagesInBulkTest.xml @@ -19,9 +19,17 @@ <group value="media_gallery_ui"/> </annotations> <before> + <createData entity="SimpleSubCategory" stepKey="category"/> <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> - <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> + <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="openCategoryPage"/> + <actionGroup ref="AdminCategoriesOpenCategoryActionGroup" stepKey="openCategory"> + <argument name="category" value="$$category$$"/> + </actionGroup> + <actionGroup ref="AdminOpenMediaGalleryFromCategoryImageUploaderActionGroup" stepKey="openMediaGalleryFromWysiwyg"/> </before> + <after> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + </after> <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> <argument name="image" value="ImageUpload"/> </actionGroup> @@ -34,7 +42,7 @@ <argument name="imageName" value="{{ImageUpload.fileName}}"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryDisableMassactionModeActionGroup" stepKey="disableMassActionMode"/> - + <actionGroup ref="AdminEnhancedMediaGalleryEnableMassActionModeActionGroup" stepKey="enableMassActionToDeleteImages"/> <actionGroup ref="AdminEnhancedMediaGallerySelectImageForMassActionActionGroup" stepKey="selectFirstImageToDelete"> <argument name="imageName" value="{{ImageUpload.fileName}}"/> @@ -44,7 +52,9 @@ </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryClickDeleteImagesButtonActionGroup" stepKey="clikDeleteSelectedButton"/> <actionGroup ref="AdminEnhancedMediaGalleryConfirmDeleteImagesActionGroup" stepKey="deleteImages"/> - <actionGroup ref="AdminEnhancedMediaGalleryAssertImagesDeletedInBulkActionGroup" stepKey="assertImagesDeleted"/> + <actionGroup ref="AdminEnhancedMediaGalleryAssertImagesDeletedInBulkActionGroup" stepKey="assertImagesDeleted"> + <argument name="numberOfAssetsDeleted" value="2"/> + </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryAssertMassActionModeNotActiveActionGroup" stepKey="assertMassectionModeDisabled"/> </test> </tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml index bd7e4fcf7a9a2..9a08f7cd0bb9c 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyAssetFilterTest.xml @@ -65,16 +65,21 @@ <actionGroup ref="AdminSaveCategoryActionGroup" stepKey="saveCategory"/> <actionGroup ref="AdminOpenCategoryGridPageActionGroup" stepKey="openCategoryGridPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="firstResetAdminDataGridToDefaultView"/> <actionGroup ref="AdminEnhancedMediaGalleryCategoryGridExpandFilterActionGroup" stepKey="expandFilters"/> <actionGroup ref="AdminEnhancedMediaGallerySelectUsedInFilterActionGroup" stepKey="setUsedInFilter"> <argument name="filterName" value="Asset"/> <argument name="optionName" value="{{ImageMetadata.title}}"/> </actionGroup> <actionGroup ref="AdminEnhancedMediaGalleryCategoryGridApplyFiltersActionGroup" stepKey="applyFilters"/> - - <actionGroup ref="AdminMediaGalleryAssertCategoryNameInCategoryGridActionGroup" stepKey="assertCategoryInGrid"> - <argument name="categoryName" value="$$category.name$$"/> + <actionGroup ref="AssertAdminCategoryGridPageNumberOfRecordsActionGroup" stepKey="assertOneRecordInGrid"> + <argument name="numberOfRecords" value="1 records found"/> + </actionGroup> + <actionGroup ref="AssertAdminCategoryGridPageImageColumnActionGroup" stepKey="assertCategoryGridPageImageColumn"/> + <actionGroup ref="AssertAdminCategoryGridPageDetailsActionGroup" stepKey="assertCategoryInGrid"> + <argument name="category" value="$$category$$"/> </actionGroup> - <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AssertAdminCategoryGridPageProductsInMenuEnabledColumnsActionGroup" stepKey="assertCategoryGridPageProductsInMenuEnabledColumns"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> </test> </tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml new file mode 100644 index 0000000000000..f47d6d9202c05 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminEnhancedMediaGalleryVerifyUpdatedTagsTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminEnhancedMediaGalleryVerifyUpdatedTagsTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1703"/> + <title value="User checks if the deleted tags are removed from Edit page, Tags field"/> + <stories value="User checks if the deleted tags are removed from Edit page, Tags field"/> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1337102/scenarios/5064888"/> + <description value="User checks if changes made on the tags are updated from Edit page, Tags field"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openMediaGallery"/> + </before> + <after> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsDeleteActionGroup" stepKey="deleteImage"/> + </after> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload3"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="viewImageDetails"/> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsEditActionGroup" stepKey="editImage"/> + <actionGroup ref="AdminMediaGalleryEditAssetAddKeywordActionGroup" stepKey="setKeywords"> + <argument name="keyword" value="UpdatedImageDetails.keyword"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveImage"> + <argument name="image" value="UpdatedImageDetails"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageKeywordsActionGroup" stepKey="verifyAddedKeywords"> + <argument name="keywords" value="UpdatedImageDetails.keyword"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsEditActionGroup" stepKey="updateImageDetails"/> + <actionGroup ref="AdminMediaGalleryEditAssetRemoveKeywordActionGroup" stepKey="removeKeywords"> + <argument name="keyword" value="{{UpdatedImageDetails.keyword}}"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveUpdatedImage"> + <argument name="image" value="UpdatedImageDetails"/> + </actionGroup> + <actionGroup ref="AssetAdminEnhancedMediaGalleryAssetDetailsKeywordsAbsentActionGroup" stepKey="verifyRemovedKeywords"> + <argument name="keywords" value="{{UpdatedImageDetails.keyword}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryAscendingTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryAscendingTest.xml new file mode 100644 index 0000000000000..4dbf3da0752b2 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryAscendingTest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGallerySortByDirectoryAscendingTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Directory Ascending in Standalone Media Gallery"/> + <stories value="User uses Sort by Directory Ascending in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Directory Ascending in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByDirectoryAscending"> + <argument name="sortName" value="directory_asc"/> + </actionGroup> + + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByDirectoryAscending"> + <argument name="firstImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="secondImageFile" value="{{ImageUpload.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload1.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryDescendingTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryDescendingTest.xml new file mode 100644 index 0000000000000..025da24511b73 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByDirectoryDescendingTest.xml @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGallerySortByDirectoryDescendingTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Directory Descending in Standalone Media Gallery"/> + <stories value="User uses Sort by Directory Descending in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Directory Descending in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByDirectoryDescending"> + <argument name="sortName" value="directory_desc"/> + </actionGroup> + + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByDirectoryDescending"> + <argument name="firstImageFile" value="{{ImageUpload1.value}}"/> + <argument name="secondImageFile" value="{{ImageUpload.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload_1.file}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameAToZTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameAToZTest.xml new file mode 100644 index 0000000000000..da0d8a18b75e4 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameAToZTest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGallerySortByNameAToZTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Name A to Z in Standalone Media Gallery"/> + <stories value="User uses Sort by Name A to Z in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Name A to Z in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByNameAToZ"> + <argument name="sortName" value="name_az"/> + </actionGroup> + + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByNameAToZ"> + <argument name="firstImageFile" value="{{ImageUpload.file}}"/> + <argument name="secondImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload1.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameZToATest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameZToATest.xml new file mode 100644 index 0000000000000..4b5086e5d63ff --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNameZToATest.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGallerySortByNameZToATest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Name Z to A in Standalone Media Gallery"/> + <stories value="User uses Sort by Name Z to A in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Name Z to A in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByNameZToA"> + <argument name="sortName" value="name_za"/> + </actionGroup> + + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByNameZToA"> + <argument name="firstImageFile" value="{{ImageUpload1.value}}"/> + <argument name="secondImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload.file}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNewestFirstTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNewestFirstTest.xml new file mode 100644 index 0000000000000..4274b26d5770f --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByNewestFirstTest.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGallerySortByNewestFirstTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Newest First in Standalone Media Gallery"/> + <stories value="User uses Sort by Newest First in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Newest First in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByNewestFirst"> + <argument name="sortName" value="newest_first"/> + </actionGroup> + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByNewestFirst"> + <argument name="firstImageFile" value="{{ImageUpload1.value}}"/> + <argument name="secondImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload.file}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByOldestFirstTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByOldestFirstTest.xml new file mode 100644 index 0000000000000..e67fdcfcf40b3 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySortByOldestFirstTest.xml @@ -0,0 +1,84 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGallerySortByOldestFirstTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <title value="User uses Sort by Oldest First in Standalone Media Gallery"/> + <stories value="User uses Sort by Oldest First in Standalone Media Gallery"/> + <testCaseId value="https://github.com/magento/adobe-stock-integration/issues/1776"/> + <description value="User uses Sort by Oldest First in Standalone Media Gallery"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFirstFolderForDelete"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFirstFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFirstFolderWasDeleted"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <waitForLoadingMaskToDisappear stepKey="waitForLoadingMaskToDisappear"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectSecondFolderForDelete"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteSecondFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertSecondFolderWasDeleted"> + <argument name="name" value="secondFolder"/> + </actionGroup> + </after> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGalleryPage"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openFirstNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createFirstNewFolder"> + <argument name="name" value="firstFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertFirstNewFolderCreated"> + <argument name="name" value="firstFolder"/> + </actionGroup> + + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> + <argument name="image" value="ImageUpload"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadSecondImage"> + <argument name="image" value="ImageUpload_1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="waitForGridToLoad"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openSecondNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createSecondNewFolder"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertSecondNewFolderCreated"> + <argument name="name" value="secondFolder"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadThirdImage"> + <argument name="image" value="ImageUpload1"/> + </actionGroup> + + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="secondResetAdminDataGridToDefaultView"/> + <waitForPageLoad stepKey="secondWaitForGridToLoad"/> + + <actionGroup ref="AdminEnhancedMediaGalleryClickSortActionGroup" stepKey="sortByOldestFirst"> + <argument name="sortName" value="oldest_first"/> + </actionGroup> + <actionGroup ref="AssertAdminEnhancedMediaGallerySortByActionGroup" stepKey="assertImagePositionAfterSortByOldestFirst"> + <argument name="firstImageFile" value="{{ImageUpload.file}}"/> + <argument name="secondImageFile" value="{{ImageUpload_1.file}}"/> + <argument name="thirdImageFile" value="{{ImageUpload1.value}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySwitchingBetweenViewsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySwitchingBetweenViewsTest.xml new file mode 100644 index 0000000000000..01b8c27b7371d --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGallerySwitchingBetweenViewsTest.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminMediaGallerySwitchingBetweenViewsTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1523"/> + <title value="User switches between Views and checks if the folder is changed"/> + <stories value="User switches between Views and checks if the folder is changed"/> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1337102/scenarios/5060037"/> + <description value="User switches between Views and checks if the folder is changed"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="category"/> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <after> + <actionGroup ref="ResetAdminDataGridToDefaultViewActionGroup" stepKey="resetAdminDataGridToDefaultView"/> + <actionGroup ref="AdminEnhancedMediaGalleryDeleteGridViewActionGroup" stepKey="deleteView"> + <argument name="viewToDelete" value="New View"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectFolderForDelete"/> + <actionGroup ref="AdminMediaGalleryFolderDeleteActionGroup" stepKey="deleteFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderDoesNotExistActionGroup" stepKey="assertFolderWasDeleted"/> + <deleteData createDataKey="category" stepKey="deleteCategory"/> + </after> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="openNewPage"/> + <actionGroup ref="AdminOpenMediaGalleryFromPageNoEditorActionGroup" stepKey="openMediaGalleryForPage"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearFilters"/> + <actionGroup ref="AdminMediaGalleryOpenNewFolderFormActionGroup" stepKey="openNewFolderForm"/> + <actionGroup ref="AdminMediaGalleryCreateNewFolderActionGroup" stepKey="createNewFolder"/> + <actionGroup ref="AdminMediaGalleryAssertFolderNameActionGroup" stepKey="assertNewFolderCreated"/> + <waitForLoadingMaskToDisappear stepKey="waitForFolderContents"/> + <actionGroup ref="AdminEnhancedMediaGallerySaveCustomViewActionGroup" stepKey="saveCustomView"> + <argument name="viewName" value="New View"/> + </actionGroup> + <actionGroup ref="AdminOpenCategoryPageActionGroup" stepKey="openCategoryPage"/> + <actionGroup ref="AdminCategoriesOpenCategoryActionGroup" stepKey="openCategory"> + <argument name="category" value="$$category$$"/> + </actionGroup> + <actionGroup ref="AdminOpenMediaGalleryFromCategoryImageUploaderActionGroup" stepKey="openMediaGalleryFromImageUploader"/> + <actionGroup ref="AdminEnhancedMediaGallerySelectCustomBookmarksViewActionGroup" stepKey="selectDefaultView"> + <argument name="selectView" value="Default View"/> + </actionGroup> + <actionGroup ref="AssertFolderIsChangedActionGroup" stepKey="assertFolderIsChanged"> + <argument name="newSelectedFolder" value="category" /> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGallerySelectCustomBookmarksViewActionGroup" stepKey="switchBackToNewView"> + <argument name="selectView" value="New View"/> + </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryAssertActiveFiltersActionGroup" stepKey="assertFilterApplied"> + <argument name="resultValue" value="{{AdminMediaGalleryFolderData.name}}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml index ca7a71258fead..684db1d4a2627 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminMediaGalleryUploadCategoryImageTest.xml @@ -36,8 +36,15 @@ <actionGroup ref="AddCategoryImageActionGroup" stepKey="addCategoryImage"/> <actionGroup ref="AdminSaveCategoryFormActionGroup" stepKey="saveCategoryForm"/> <actionGroup ref="AdminOpenMediaGalleryFromCategoryImageUploaderActionGroup" stepKey="openMediaGalleryFromImageUploader"/> + <actionGroup ref="ClearFiltersAdminDataGridActionGroup" stepKey="clearFilter"/> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectCatalogFolder"> + <argument name="name" value="catalog"/> + </actionGroup> + <actionGroup ref="AdminMediaGalleryFolderSelectActionGroup" stepKey="selectCategoryFolder"> + <argument name="name" value="category"/> + </actionGroup> <actionGroup ref="AdminMediaGalleryAssertImageInGridActionGroup" stepKey="assertImageInGrid"> - <argument name="title" value="ProductImage.filename"/> + <argument name="title" value="ProductImage.fileName"/> </actionGroup> </test> </tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryDisabledTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryDisabledTest.xml new file mode 100644 index 0000000000000..8b0c984c1df77 --- /dev/null +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryDisabledTest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminStandaloneMediaGalleryDisabledTest"> + <annotations> + <features value="MediaGallery"/> + <useCaseId value="https://github.com/magento/adobe-stock-integration/issues/1760"/> + <title value="Standalone Media Gallery Page should return 404 if Media Gallery is disabled"/> + <stories value="#1760 Media Gallery Page opened successfully if Enhanced Media Gallery disabled"/> + <testCaseId value="https://studio.cucumber.io/projects/131313/test-plan/folders/1337102/scenarios/5106786"/> + <description value="Standalone Media Gallery Page should return 404 if Media Gallery is disabled"/> + <severity value="CRITICAL"/> + <group value="media_gallery_ui_disabled"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + </before> + <actionGroup ref="AdminOpenStandaloneMediaGalleryActionGroup" stepKey="openStandaloneMediaGallery"/> + <actionGroup ref="AssertAdminPageIs404ActionGroup" stepKey="see404Page"/> + </test> +</tests> diff --git a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml index ede3a452e4ca5..58c6f32b8d72f 100644 --- a/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml +++ b/app/code/Magento/MediaGalleryUi/Test/Mftf/Test/AdminStandaloneMediaGalleryEditImageDetailsTest.xml @@ -29,6 +29,10 @@ <actionGroup ref="AdminEnhancedMediaGalleryUploadImageActionGroup" stepKey="uploadImage"> <argument name="image" value="ImageUpload"/> </actionGroup> + <actionGroup ref="AdminEnhancedMediaGalleryViewImageDetails" stepKey="clickViewDetails"/> + <actionGroup ref="AssertAdminEnhancedMediaGalleryUploadedImageDateTimeEqualsActionGroup" stepKey="verifyCreatedAndUpdatedAtDate" /> + <wait time="100" stepKey="waitForUpdateTimeToBeGreater"/> + <actionGroup ref="AdminEnhancedMediaGalleryCloseViewDetailsActionGroup" stepKey="closeViewDetails"/> <actionGroup ref="AdminEnhancedMediaGalleryEditImageDetailsActionGroup" stepKey="editImageDetails"/> <actionGroup ref="AdminEnhancedMediaGalleryImageDetailsSaveActionGroup" stepKey="saveImage"> <argument name="image" value="UpdatedImageDetails"/> @@ -40,6 +44,7 @@ <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageDetailsActionGroup" stepKey="verifyImageDetails"> <argument name="image" value="UpdatedImageDetails"/> </actionGroup> + <actionGroup ref="AssertAdminEnhancedMediaGalleryImageCreatedAtNotEqualsUpdatedAtTimeActionGroup" stepKey="assertUpdatedAtTimeChanged" /> <actionGroup ref="AdminEnhancedMediaGalleryVerifyImageDescriptionActionGroup" stepKey="verifyImageDescription"> <argument name="description" value="UpdatedImageDetails.description"/> </actionGroup> diff --git a/app/code/Magento/MediaGalleryUi/Ui/Component/DirectoriesTree.php b/app/code/Magento/MediaGalleryUi/Ui/Component/DirectoryTree.php similarity index 81% rename from app/code/Magento/MediaGalleryUi/Ui/Component/DirectoriesTree.php rename to app/code/Magento/MediaGalleryUi/Ui/Component/DirectoryTree.php index 4047a4fcb98d8..269bc1f8bcba7 100644 --- a/app/code/Magento/MediaGalleryUi/Ui/Component/DirectoriesTree.php +++ b/app/code/Magento/MediaGalleryUi/Ui/Component/DirectoryTree.php @@ -14,7 +14,7 @@ /** * Directories tree component */ -class DirectoriesTree extends Container +class DirectoryTree extends Container { /** * @var UrlInterface @@ -50,9 +50,9 @@ public function prepare(): void array_replace_recursive( (array) $this->getData('config'), [ - 'getDirectoryTreeUrl' => $this->url->getUrl("media_gallery/directories/gettree"), - 'deleteDirectoryUrl' => $this->url->getUrl("media_gallery/directories/delete"), - 'createDirectoryUrl' => $this->url->getUrl("media_gallery/directories/create") + 'getDirectoryTreeUrl' => $this->url->getUrl('media_gallery/directories/gettree'), + 'deleteDirectoryUrl' => $this->url->getUrl('media_gallery/directories/delete'), + 'createDirectoryUrl' => $this->url->getUrl('media_gallery/directories/create') ] ) ); diff --git a/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php b/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php index 273cf9e37554b..f61e34512bfe3 100644 --- a/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php +++ b/app/code/Magento/MediaGalleryUi/Ui/Component/Listing/Filters/Asset.php @@ -17,7 +17,7 @@ use Magento\Ui\Component\Filters\Type\Select; /** - * Asset filter + * Asset filter */ class Asset extends Select { @@ -27,6 +27,8 @@ class Asset extends Select private $getContentIdentities; /** + * Constructor + * * @param ContextInterface $context * @param UiComponentFactory $uiComponentFactory * @param FilterBuilder $filterBuilder @@ -35,6 +37,7 @@ class Asset extends Select * @param GetContentByAssetIdsInterface $getContentIdentities * @param array $components * @param array $data + * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ public function __construct( ContextInterface $context, @@ -67,17 +70,20 @@ public function __construct( */ public function applyFilter() { - if (isset($this->filterData[$this->getName()])) { - $ids = is_array($this->filterData[$this->getName()]) - ? $this->filterData[$this->getName()] - : [$this->filterData[$this->getName()]]; - $filter = $this->filterBuilder->setConditionType('in') - ->setField($this->_data['config']['identityColumn']) - ->setValue($this->getEntityIdsByAsset($ids)) - ->create(); + if (!isset($this->filterData[$this->getName()])) { + return; + } - $this->getContext()->getDataProvider()->addFilter($filter); + $assetIds = $this->filterData[$this->getName()]; + if (!is_array($assetIds)) { + $assetIds = explode(',', str_replace(['[', ']'], '', $assetIds)); } + + $filter = $this->filterBuilder->setConditionType('in') + ->setField($this->_data['config']['identityColumn']) + ->setValue($this->getEntityIdsByAsset($assetIds)) + ->create(); + $this->getContext()->getDataProvider()->addFilter($filter); } /** diff --git a/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml b/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml index 77544b42e899a..17aa08b5363ca 100644 --- a/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml +++ b/app/code/Magento/MediaGalleryUi/etc/adminhtml/system.xml @@ -9,10 +9,10 @@ <system> <section id="system"> <group id="media_gallery" translate="label" type="text" sortOrder="1000" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Enhanced Media Gallery</label> + <label>Media Gallery</label> <field id="enabled" translate="label" type="select" sortOrder="10" showInDefault="1" showInWebsite="0" showInStore="0"> - <label>Enabled</label> - <source_model>Magento\Config\Model\Config\Source\Yesno</source_model> + <label>Enable Old Media Gallery</label> + <source_model>Magento\MediaGalleryUi\Model\Config\MediaGallery\Yesno</source_model> <config_path>system/media_gallery/enabled</config_path> </field> </group> diff --git a/app/code/Magento/MediaGalleryUi/etc/di.xml b/app/code/Magento/MediaGalleryUi/etc/di.xml index a8c4e2a8d8963..6ed3a98bbf03a 100644 --- a/app/code/Magento/MediaGalleryUi/etc/di.xml +++ b/app/code/Magento/MediaGalleryUi/etc/di.xml @@ -28,11 +28,6 @@ </argument> </arguments> </type> - <type name="Magento\MediaGalleryUi\Model\Directories\GetFolderTree"> - <arguments> - <argument name="path" xsi:type="string">media</argument> - </arguments> - </type> <type name="Magento\MediaGallerySynchronizationApi\Model\ImportFilesComposite"> <plugin name="createMediaGalleryThumbnails" type="Magento\MediaGalleryUi\Plugin\CreateThumbnails"/> </type> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details.phtml b/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details.phtml index ba2033478afa1..a6da20a255192 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details.phtml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details.phtml @@ -74,12 +74,6 @@ use Magento\Framework\Escaper; "imageModelName" : "media_gallery_listing.media_gallery_listing.media_gallery_columns.thumbnail_url", "mediaGalleryImageDetailsName": "mediaGalleryImageDetails", "actionsList": [ - { - "title": "<?= $escaper->escapeJs(__('Edit Details')); ?>", - "handler": "editImageAction", - "name": "edit", - "classes": "action-default scalable edit action-quaternary" - }, { "title": "<?= $escaper->escapeJs(__('Cancel')); ?>", "handler": "closeModal", @@ -92,6 +86,12 @@ use Magento\Framework\Escaper; "name": "delete", "classes": "action-default scalable delete action-quaternary" }, + { + "title": "<?= $escaper->escapeJs(__('Edit Details')); ?>", + "handler": "editImageAction", + "name": "edit", + "classes": "action-default scalable edit action-quaternary" + }, { "title": "<?= $escaper->escapeJs(__('Add Image')); ?>", "handler": "addImage", diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details_standalone.phtml b/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details_standalone.phtml index 9fc0e749ac888..288a2eaee5221 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details_standalone.phtml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/templates/image_details_standalone.phtml @@ -72,12 +72,6 @@ use Magento\Backend\Block\Template; "mediaGalleryImageDetailsName": "mediaGalleryImageDetails", "imageModelName" : "standalone_media_gallery_listing.standalone_media_gallery_listing.media_gallery_columns.thumbnail_url", "actionsList": [ - { - "title": "<?= $escaper->escapeJs(__('Edit Details')); ?>", - "handler": "editImageAction", - "name": "edit", - "classes": "action-default scalable edit action-quaternary" - }, { "title": "<?= $escaper->escapeJs(__('Cancel')); ?>", "handler": "closeModal", @@ -89,6 +83,12 @@ use Magento\Backend\Block\Template; "handler": "deleteImageAction", "name": "delete", "classes": "action-default scalable delete action-quaternary" + }, + { + "title": "<?= $escaper->escapeJs(__('Edit Details')); ?>", + "handler": "editImageAction", + "name": "edit", + "classes": "action-default scalable edit action-quaternary" } ] } diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_block_listing.xml b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_block_listing.xml index 86c8590bb4860..20988fad5ff35 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_block_listing.xml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_block_listing.xml @@ -13,7 +13,7 @@ provider="${ $.parentName }" sortOrder="10" class="Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="Magento_MediaGalleryUi/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -24,6 +24,7 @@ <item name="searchOptions" xsi:type="boolean">true</item> <item name="filterOptions" xsi:type="boolean">true</item> <item name="searchUrl" xsi:type="url" path="media_gallery/asset/search" /> + <item name="validationUrl" xsi:type="url" path="media_gallery/asset/getSelected"/> <item name="levelsVisibility" xsi:type="number">1</item> </item> </argument> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_page_listing.xml b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_page_listing.xml index 58881a8c9de6c..4abeb71679bde 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_page_listing.xml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/cms_page_listing.xml @@ -13,7 +13,7 @@ provider="${ $.parentName }" sortOrder="10" class="Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="Magento_MediaGalleryUi/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -24,6 +24,7 @@ <item name="filterPlaceholder" xsi:type="string" translate="true">Asset Title</item> <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find assets</item> <item name="searchUrl" xsi:type="url" path="media_gallery/asset/search" /> + <item name="validationUrl" xsi:type="url" path="media_gallery/asset/getSelected"/> <item name="levelsVisibility" xsi:type="number">1</item> </item> </argument> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/media_gallery_listing.xml b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/media_gallery_listing.xml index 49206043725f9..66731b1cbae6f 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/media_gallery_listing.xml @@ -219,7 +219,7 @@ </container> </listingToolbar> <container name="media_gallery_directories" - class="Magento\MediaGalleryUi\Ui\Component\DirectoriesTree" + class="Magento\MediaGalleryUi\Ui\Component\DirectoryTree" template="Magento_MediaGalleryUi/grid/directories/directoryTree" component="Magento_MediaGalleryUi/js/directory/directoryTree"/> <columns name="media_gallery_columns" component="Magento_MediaGalleryUi/js/grid/masonry"> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/product_listing.xml index 2b7d9fde3b9ff..4634652e9cc19 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/product_listing.xml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/product_listing.xml @@ -13,7 +13,7 @@ provider="${ $.parentName }" sortOrder="10" class="Magento\MediaGalleryUi\Ui\Component\Listing\Filters\Asset" - component="Magento_Ui/js/form/element/ui-select" + component="Magento_Ui/js/grid/filters/elements/ui-select" template="Magento_MediaGalleryUi/grid/filters/elements/ui-select"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> @@ -24,6 +24,7 @@ <item name="filterPlaceholder" xsi:type="string" translate="true">Asset Title</item> <item name="emptyOptionsHtml" xsi:type="string" translate="true">Start typing to find assets</item> <item name="searchUrl" xsi:type="url" path="media_gallery/asset/search" /> + <item name="validationUrl" xsi:type="url" path="media_gallery/asset/getSelected"/> <item name="levelsVisibility" xsi:type="number">1</item> </item> </argument> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml index 655178c104492..3656a8ea25f74 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/ui_component/standalone_media_gallery_listing.xml @@ -206,7 +206,7 @@ </container> </listingToolbar> <container name="media_gallery_directories" - class="Magento\MediaGalleryUi\Ui\Component\DirectoriesTree" + class="Magento\MediaGalleryUi\Ui\Component\DirectoryTree" template="Magento_MediaGalleryUi/grid/directories/directoryTree" component="Magento_MediaGalleryUi/js/directory/directoryTree"/> <columns name="media_gallery_columns" component="Magento_MediaGalleryUi/js/grid/masonry"> diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/css/source/_module.less b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/css/source/_module.less index fc8bd49126d8e..6b3cd610f0348 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/css/source/_module.less +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/css/source/_module.less @@ -18,7 +18,7 @@ @color-media-gallery-buttons-border: #adadad; @color-media-gallery-buttons-text: #514943; @color-media-gallery-checkbox-background: #eee; - +@color-media-gallery-scrollbar-background: #fff; & when (@media-common = true) { .media-gallery-delete-image-action, @@ -170,8 +170,9 @@ height: 30px; margin: 1px; padding-left: 6px; + padding-right: 10px; padding-top: 6px; - width: 100%; + width: max-content; } .jstree-default .jstree-clicked { @@ -272,8 +273,18 @@ } .media-directory-container { + &::-webkit-scrollbar { + background-color: @color-media-gallery-scrollbar-background; + } + &::-webkit-scrollbar-thumb { + background-color: @color-masonry-grey; + } float: left; + max-width: 50%; + overflow-x: scroll; + overflow-y: hidden; padding-right: 40px; + scrollbar-color: @color-masonry-grey @color-media-gallery-scrollbar-background; } .media-gallery-image-block { diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/action/deleteImageWithDetailConfirmation.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/action/deleteImageWithDetailConfirmation.js index ed40674df20f0..28c021fe4728f 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/action/deleteImageWithDetailConfirmation.js +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/action/deleteImageWithDetailConfirmation.js @@ -21,25 +21,24 @@ define([ * @param {String} deleteImageUrl */ deleteImageAction: function (recordsIds, imageDetailsUrl, deleteImageUrl) { - var confirmationContent = $t('%1 Are you sure you want to delete "%2" image(s)?') + var confirmationContent = $t('%1Are you sure you want to delete "%2" image(s)?') .replace('%2', Object.keys(recordsIds).length), deferred = $.Deferred(); - getDetails(imageDetailsUrl, recordsIds) - .then(function (imageDetails) { + getDetails(imageDetailsUrl, recordsIds).then(function (images) { confirmationContent = confirmationContent.replace( '%1', - this.getRecordRelatedContentMessage(imageDetails) + this.getRecordRelatedContentMessage(images) + ' ' ); }.bind(this)).fail(function () { - confirmationContent = confirmationContent.replace('%1', ''); - }).always(function () { - deleteImages(recordsIds, deleteImageUrl, confirmationContent).then(function (status) { - deferred.resolve(status); - }).fail(function (error) { - deferred.reject(error); - }); - }); + confirmationContent = confirmationContent.replace('%1', ''); + }).always(function () { + deleteImages(recordsIds, deleteImageUrl, confirmationContent).then(function (status) { + deferred.resolve(status); + }).fail(function (error) { + deferred.reject(error); + }); + }); return deferred.promise(); }, diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/directory/directories.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/directory/directories.js index d7f756d8bbd90..6d8d38a1ca1d6 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/directory/directories.js +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/directory/directories.js @@ -23,6 +23,7 @@ define([ deleteButtonSelector: '#delete_folder', createFolderButtonSelector: '#create_folder', messageDelay: 5, + selectedFolder: null, messagesName: 'media_gallery_listing.media_gallery_listing.messages', modules: { directoryTree: '${ $.parentName }.media_gallery_directories', @@ -47,51 +48,57 @@ define([ */ initEvents: function () { $(this.deleteButtonSelector).on('delete_folder', function () { - this.getConfirmationPopupDeleteFolder(); + this.deleteFolder(); }.bind(this)); $(this.createFolderButtonSelector).on('create_folder', function () { - this.getPrompt({ - title: $t('New Folder Name:'), - content: '', - actions: { - /** - * Confirm action - */ - confirm: function (folderName) { - createDirectory( - this.directoryTree().createDirectoryUrl, - [this.getNewFolderPath(folderName)] - ).then(function () { - this.directoryTree().reloadJsTree().then(function () { - $(this.directoryTree().directoryTreeSelector).on('loaded.jstree', function () { - this.directoryTree().locateNode(this.getNewFolderPath(folderName)); - }.bind(this)); - }.bind(this)); + this.createFolder(); + }.bind(this)); + }, - }.bind(this)).fail(function (error) { - uiAlert({ - content: error - }); + /** + * Show confirmation popup and create folder based on user input + */ + createFolder: function () { + this.getPrompt({ + title: $t('New Folder Name:'), + content: '', + actions: { + /** + * Confirm action + */ + confirm: function (folderName) { + createDirectory( + this.directoryTree().createDirectoryUrl, + [this.getNewFolderPath(folderName)] + ).then(function () { + this.directoryTree().reloadJsTree().then(function () { + $(this.directoryTree().directoryTreeSelector).on('loaded.jstree', function () { + this.directoryTree().locateNode(this.getNewFolderPath(folderName)); + }.bind(this)); + }.bind(this)); + }.bind(this)).fail(function (error) { + uiAlert({ + content: error }); - }.bind(this) - }, - buttons: [{ - text: $t('Cancel'), - class: 'action-secondary action-dismiss', + }); + }.bind(this) + }, + buttons: [{ + text: $t('Cancel'), + class: 'action-secondary action-dismiss', - /** - * Close modal - */ - click: function () { - this.closeModal(); - } - }, { - text: $t('Confirm'), - class: 'action-primary action-accept' - }] - }); - }.bind(this)); + /** + * Close modal + */ + click: function () { + this.closeModal(); + } + }, { + text: $t('Confirm'), + class: 'action-primary action-accept' + }] + }); }, /** @@ -101,11 +108,11 @@ define([ * @returns {String} */ getNewFolderPath: function (folderName) { - var selectedFolder = _.isUndefined(this.selectedFolder()) || - _.isNull(this.selectedFolder()) ? '/' : this.selectedFolder(), - folderToCreate = selectedFolder !== '/' ? selectedFolder + '/' + folderName : folderName; + if (_.isUndefined(this.selectedFolder()) || _.isNull(this.selectedFolder())) { + return folderName; + } - return folderToCreate; + return this.selectedFolder() + '/' + folderName; }, /** @@ -136,7 +143,7 @@ define([ /** * Confirmation popup for delete folder action. */ - getConfirmationPopupDeleteFolder: function () { + deleteFolder: function () { confirm({ title: $t('Are you sure you want to delete this folder?'), modalClass: 'delete-folder-confirmation-popup', diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/directory/directoryTree.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/directory/directoryTree.js index decc337e1b83c..2e1e9a980cd59 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/directory/directoryTree.js +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/directory/directoryTree.js @@ -20,13 +20,15 @@ define([ filterChipsProvider: 'componentType = filters, ns = ${ $.ns }', directoryTreeSelector: '#media-gallery-directory-tree', getDirectoryTreeUrl: 'media_gallery/directories/gettree', + createDirectoryUrl: 'media_gallery/directories/create', + deleteDirectoryUrl: 'media_gallery/directories/delete', jsTreeReloaded: null, modules: { directories: '${ $.name }_directories', filterChips: '${ $.filterChipsProvider }' }, listens: { - '${ $.provider }:params.filters.path': 'clearFiltersHandle' + '${ $.provider }:params.filters.path': 'updateSelectedDirectory' }, viewConfig: [{ component: 'Magento_MediaGalleryUi/js/directory/directories', @@ -49,7 +51,8 @@ define([ this.renderDirectoryTree().then(function () { this.initEvents(); }.bind(this)); - }.bind(this)); + }.bind(this) + ); return this; }, @@ -58,7 +61,6 @@ define([ * Render directory tree component. */ renderDirectoryTree: function () { - return this.getJsonTree().then(function (data) { this.createFolderIfNotExists(data).then(function (isFolderCreated) { if (isFolderCreated) { @@ -87,37 +89,37 @@ define([ * @param {Array} directories */ createFolderIfNotExists: function (directories) { - var isMediaBrowser = !_.isUndefined(window.MediabrowserUtility), - currentTreePath = isMediaBrowser ? window.MediabrowserUtility.pathId : null, + var requestedDirectory = this.getRequestedDirectory(), deferred = $.Deferred(), - decodedPath, pathArray; - if (currentTreePath) { - decodedPath = Base64.idDecode(currentTreePath); - - if (!this.isDirectoryExist(directories[0], decodedPath)) { - pathArray = this.convertPathToPathsArray(decodedPath); + if (_.isNull(requestedDirectory)) { + deferred.resolve(false); - $.each(pathArray, function (i, val) { - if (this.isDirectoryExist(directories[0], val)) { - pathArray.splice(i, 1); - } - }.bind(this)); + return deferred.promise(); + } - createDirectory( - this.createDirectoryUrl, - pathArray - ).then(function () { - deferred.resolve(true); - }); - } else { - deferred.resolve(false); - } - } else { + if (this.isDirectoryExist(directories[0], requestedDirectory)) { deferred.resolve(false); + + return deferred.promise(); } + pathArray = this.convertPathToPathsArray(requestedDirectory); + + $.each(pathArray, function (i, val) { + if (this.isDirectoryExist(directories[0], val)) { + pathArray.splice(i, 1); + } + }.bind(this)); + + createDirectory( + this.createDirectoryUrl, + pathArray + ).then(function () { + deferred.resolve(true); + }); + return deferred.promise(); }, @@ -199,7 +201,7 @@ define([ /** * Remove ability to multiple select on nodes */ - overrideMultiselectBehavior: function () { + disableMultiselectBehavior: function () { $.jstree.defaults.ui['select_range_modifier'] = false; $.jstree.defaults.ui['select_multiple_modifier'] = false; }, @@ -208,8 +210,8 @@ define([ * Handle jstree events */ initEvents: function () { - this.firejsTreeEvents(); - this.overrideMultiselectBehavior(); + this.initJsTreeEvents(); + this.disableMultiselectBehavior(); $(window).on('reload.MediaGallery', function () { this.getJsonTree().then(function (data) { @@ -217,10 +219,10 @@ define([ if (isCreated) { this.renderDirectoryTree().then(function () { this.setJsTreeReloaded(true); - this.firejsTreeEvents(); + this.initJsTreeEvents(); }.bind(this)); } else { - this.checkChipFiltersState(); + this.updateSelectedDirectory(); } }.bind(this)); }.bind(this)); @@ -230,30 +232,33 @@ define([ /** * Fire event for jstree component */ - firejsTreeEvents: function () { + initJsTreeEvents: function () { $(this.directoryTreeSelector).on('select_node.jstree', function (element, data) { - var path = $(data.rslt.obj).data('path'); - - this.setActiveNodeFilter(path); + this.setActiveNodeFilter($(data.rslt.obj).data('path')); this.setJsTreeReloaded(false); }.bind(this)); $(this.directoryTreeSelector).on('loaded.jstree', function () { - this.checkChipFiltersState(); + this.updateSelectedDirectory(); }.bind(this)); - }, /** * Verify directory filter on init event, select folder per directory filter state */ - checkChipFiltersState: function () { + updateSelectedDirectory: function () { var currentFilterPath = this.filterChips().filters.path, - isMediaBrowser = !_.isUndefined(window.MediabrowserUtility), + requestedDirectory = this.getRequestedDirectory(), currentTreePath; - currentTreePath = this.isFiltersApplied(currentFilterPath) || !isMediaBrowser ? currentFilterPath : - Base64.idDecode(window.MediabrowserUtility.pathId); + if (_.isUndefined(currentFilterPath)) { + this.clearFiltersHandle(); + + return; + } + + currentTreePath = this.isFilterApplied(currentFilterPath) || _.isNull(requestedDirectory) ? + currentFilterPath : requestedDirectory; if (this.folderExistsInTree(currentTreePath)) { this.locateNode(currentTreePath); @@ -275,14 +280,23 @@ define([ return false; }, + /** + * Get requested directory from MediabrowserUtility + * + * @returns {String|null} + */ + getRequestedDirectory: function () { + return !_.isUndefined(window.MediabrowserUtility) && window.MediabrowserUtility.pathId !== '' ? + Base64.idDecode(window.MediabrowserUtility.pathId) : null; + }, + /** * Check if need to select directory by filters state * * @param {String} currentFilterPath */ - isFiltersApplied: function (currentFilterPath) { - return !_.isUndefined(currentFilterPath) && currentFilterPath !== '' && - currentFilterPath !== 'wysiwyg' && currentFilterPath !== 'catalog/category'; + isFilterApplied: function (currentFilterPath) { + return !_.isUndefined(currentFilterPath) && currentFilterPath !== ''; }, /** @@ -291,9 +305,7 @@ define([ * @param {String} path */ locateNode: function (path) { - var selectedId = $(this.directoryTreeSelector).jstree('get_selected').attr('id'); - - if (path === selectedId) { + if (path === $(this.directoryTreeSelector).jstree('get_selected').attr('id')) { return; } path = path.replace(/\//g, '\\/'); @@ -303,14 +315,12 @@ define([ }, /** - * Listener to clear filters event + * Clear filters */ clearFiltersHandle: function () { - if (_.isUndefined(this.filterChips().filters.path)) { - $(this.directoryTreeSelector).jstree('deselect_all'); - this.activeNode(null); - this.directories().setInActive(); - } + $(this.directoryTreeSelector).jstree('deselect_all'); + this.activeNode(null); + this.directories().setInActive(); }, /** @@ -319,7 +329,6 @@ define([ * @param {String} nodePath */ setActiveNodeFilter: function (nodePath) { - if (this.activeNode() === nodePath && !this.jsTreeReloaded) { this.selectStorageRoot(); } else { @@ -341,14 +350,13 @@ define([ this.filterChips().set('applied', filters); this.activeNode(null); this.waitForCondition( - function () { - return _.isUndefined(this.directories()); - }.bind(this), function () { - this.directories().setInActive(); - }.bind(this) - ); - + return _.isUndefined(this.directories()); + }.bind(this), + function () { + this.directories().setInActive(); + }.bind(this) + ); }, /** @@ -372,8 +380,8 @@ define([ }, /** - * Remove active node from directory tree, and select next - */ + * Remove active node from directory tree, and select next + */ removeNode: function () { $(this.directoryTreeSelector).jstree('remove'); }, @@ -390,7 +398,6 @@ define([ filters = $.extend(true, filters, applied); filters.path = path; this.filterChips().set('applied', filters); - }, /** diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/grid/massaction/massactions.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/grid/massaction/massactions.js index 4f09854005f23..03e82e65b5db5 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/grid/massaction/massactions.js +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/grid/massaction/massactions.js @@ -141,10 +141,8 @@ define([ if (response.status === 'canceled') { return; } - this.imageModel().selected({}); - this.massActionMode(false); - this.switchMode(); - }.bind(this)); + $(window).trigger('terminateMassAction.MediaGallery'); + }); } }.bind(this)); } diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js index c7ca95bed863c..ea4de9e1feefa 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-actions.js @@ -51,6 +51,7 @@ define([ return; } + this.mediaGalleryEditDetails().keywordsSelect().cacheOptions.plain = []; modalElement.modal('closeModal'); }, @@ -86,7 +87,8 @@ define([ form = modalElement.find('#image-edit-details-form'), imageId = this.imageModel().getSelected().id, keywords = this.mediaGalleryEditDetails().selectedKeywords(), - imageDetails = this.mediaGalleryImageDetails(); + imageDetails = this.mediaGalleryImageDetails(), + imageEditDetails = this.mediaGalleryEditDetails(); if (form.validation('isValid')) { saveDetails( @@ -98,6 +100,7 @@ define([ this.closeModal(); this.imageModel().reloadGrid(); imageDetails.removeCached(imageId); + imageEditDetails.removeCached(imageId); if (imageDetails.isActive()) { imageDetails.showImageDetailsById(imageId); diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-edit.js b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-edit.js index c31bc848bdc70..e1404a16d7125 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-edit.js +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/js/image/image-edit.js @@ -223,6 +223,15 @@ define([ } return true; + }, + + /** + * Remove cached image details in edit form + * + * @param {String} id + */ + removeCached: function (id) { + delete this.images[id]; } }); }); diff --git a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/template/grid/filters/elements/ui-select.html b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/template/grid/filters/elements/ui-select.html index cce859f331d9a..a0d21672eafdb 100644 --- a/app/code/Magento/MediaGalleryUi/view/adminhtml/web/template/grid/filters/elements/ui-select.html +++ b/app/code/Magento/MediaGalleryUi/view/adminhtml/web/template/grid/filters/elements/ui-select.html @@ -77,8 +77,7 @@ </div> </if> <ul class="admin__action-multiselect-menu-inner _root" - event="{mousemove: function(data, event){onMousemove($data, $index(), event)}, - scroll: function(data, event){onScrollDown(data, event)}}"> + event="{scroll: function(data, event){onScrollDown(data, event)}}"> <each args="{ data: options, as: 'option'}"> <li class="admin__action-multiselect-menu-inner-item _root" css="{ _parent: $data.optgroup }" @@ -108,9 +107,8 @@ </if> <label class="admin__action-multiselect-label"> <span text="option.label"></span> - <img if="$parent.getPath(option)" - class="admin__action-multiselect-item-path" - attr="{ src: option.path }"/> + <img class="admin__action-multiselect-item-path" + attr="{ src: option.src }"/> </label> </div> <if args="$data.optgroup"> diff --git a/app/code/Magento/MessageQueue/etc/di.xml b/app/code/Magento/MessageQueue/etc/di.xml index f60eb5fbc20df..b283280dc4580 100644 --- a/app/code/Magento/MessageQueue/etc/di.xml +++ b/app/code/Magento/MessageQueue/etc/di.xml @@ -6,7 +6,6 @@ */ --> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> - <preference for="Magento\Framework\MessageQueue\ConfigInterface" type="Magento\Framework\MessageQueue\Config\Proxy" /> <preference for="Magento\Framework\MessageQueue\LockInterface" type="Magento\Framework\MessageQueue\Lock" /> <preference for="Magento\Framework\MessageQueue\Lock\WriterInterface" type="Magento\MessageQueue\Model\ResourceModel\Lock" /> <preference for="Magento\Framework\MessageQueue\Lock\ReaderInterface" type="Magento\MessageQueue\Model\ResourceModel\Lock" /> diff --git a/app/code/Magento/Multishipping/Controller/Checkout.php b/app/code/Magento/Multishipping/Controller/Checkout.php index 92417c7cb3a18..e4711d8766c8b 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout.php +++ b/app/code/Magento/Multishipping/Controller/Checkout.php @@ -3,88 +3,79 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Multishipping\Controller; +use Magento\Checkout\Controller\Action; +use Magento\Checkout\Controller\Express\RedirectLoginInterface; +use Magento\Checkout\Model\Session as ModelSession; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Action\Context; use Magento\Framework\App\RequestInterface; +use Magento\Framework\App\ResponseInterface; +use Magento\Framework\Controller\ResultInterface; use Magento\Framework\Exception\StateException; +use Magento\Multishipping\Helper\Url; +use Magento\Multishipping\Model\Checkout\Type\Multishipping; +use Magento\Multishipping\Model\Checkout\Type\Multishipping\State; /** * Multishipping checkout controller + * * @SuppressWarnings(PHPMD.NumberOfChildren) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -abstract class Checkout extends \Magento\Checkout\Controller\Action implements - \Magento\Checkout\Controller\Express\RedirectLoginInterface +abstract class Checkout extends Action implements RedirectLoginInterface { - /** - * Constructor - * - * @param \Magento\Framework\App\Action\Context $context - * @param \Magento\Customer\Model\Session $customerSession - * @param CustomerRepositoryInterface $customerRepository - * @param AccountManagementInterface $accountManagement - */ - public function __construct( - \Magento\Framework\App\Action\Context $context, - \Magento\Customer\Model\Session $customerSession, - CustomerRepositoryInterface $customerRepository, - AccountManagementInterface $accountManagement - ) { - parent::__construct( - $context, - $customerSession, - $customerRepository, - $accountManagement - ); - } /** * Retrieve checkout model * - * @return \Magento\Multishipping\Model\Checkout\Type\Multishipping + * @return Multishipping */ protected function _getCheckout() { - return $this->_objectManager->get(\Magento\Multishipping\Model\Checkout\Type\Multishipping::class); + return $this->_objectManager->get(Multishipping::class); } /** * Retrieve checkout state model * - * @return \Magento\Multishipping\Model\Checkout\Type\Multishipping\State + * @return State */ protected function _getState() { - return $this->_objectManager->get(\Magento\Multishipping\Model\Checkout\Type\Multishipping\State::class); + return $this->_objectManager->get(State::class); } /** * Retrieve checkout url helper * - * @return \Magento\Multishipping\Helper\Url + * @return Url */ protected function _getHelper() { - return $this->_objectManager->get(\Magento\Multishipping\Helper\Url::class); + return $this->_objectManager->get(Url::class); } /** * Retrieve checkout session * - * @return \Magento\Checkout\Model\Session + * @return ModelSession */ protected function _getCheckoutSession() { - return $this->_objectManager->get(\Magento\Checkout\Model\Session::class); + return $this->_objectManager->get(ModelSession::class); } /** * Dispatch request * * @param RequestInterface $request - * @return \Magento\Framework\App\ResponseInterface + * @return ResponseInterface * @throws \Magento\Framework\Exception\NotFoundException * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.NPathComplexity) @@ -104,7 +95,7 @@ public function dispatch(RequestInterface $request) */ if ($action == 'index') { $checkoutSessionQuote->setIsMultiShipping(true); - $this->_getCheckoutSession()->setCheckoutState(\Magento\Checkout\Model\Session::CHECKOUT_STATE_BEGIN); + $this->_getCheckoutSession()->setCheckoutState(ModelSession::CHECKOUT_STATE_BEGIN); } elseif (!$checkoutSessionQuote->getIsMultiShipping() && !in_array( $action, ['login', 'register', 'success'] @@ -116,7 +107,7 @@ public function dispatch(RequestInterface $request) } if (!in_array($action, ['login', 'register'])) { - $customerSession = $this->_objectManager->get(\Magento\Customer\Model\Session::class); + $customerSession = $this->_objectManager->get(Session::class); if (!$customerSession->authenticate($this->_getHelper()->getMSLoginUrl())) { $this->_actionFlag->set('', self::FLAG_NO_DISPATCH, true); } @@ -125,7 +116,7 @@ public function dispatch(RequestInterface $request) \Magento\Multishipping\Helper\Data::class )->isMultishippingCheckoutAvailable()) { $error = $this->_getCheckout()->getMinimumAmountError(); - $this->messageManager->addError($error); + $this->messageManager->addErrorMessage($error); $this->getResponse()->setRedirect($this->_getHelper()->getCartUrl()); $this->_actionFlag->set('', self::FLAG_NO_DISPATCH, true); return parent::dispatch($request); @@ -133,7 +124,7 @@ public function dispatch(RequestInterface $request) } $result = $this->_preDispatchValidateCustomer(); - if ($result instanceof \Magento\Framework\Controller\ResultInterface) { + if ($result instanceof ResultInterface) { return $result; } @@ -180,7 +171,7 @@ protected function _validateMinimumAmount() { if (!$this->_getCheckout()->validateMinimumAmount()) { $error = $this->_getCheckout()->getMinimumAmountError(); - $this->messageManager->addError($error); + $this->messageManager->addErrorMessage($error); $this->_forward('backToAddresses'); return false; } diff --git a/app/code/Magento/Multishipping/Controller/Checkout/AddressesPost.php b/app/code/Magento/Multishipping/Controller/Checkout/AddressesPost.php index 060a1bdd5ac4e..2a4cb47b421c0 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/AddressesPost.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/AddressesPost.php @@ -4,11 +4,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Multishipping\Controller\Checkout; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Multishipping\Controller\Checkout; use Magento\Multishipping\Model\Checkout\Type\Multishipping\State; -class AddressesPost extends \Magento\Multishipping\Controller\Checkout +class AddressesPost extends Checkout implements HttpPostActionInterface { /** * Multishipping checkout process posted addresses @@ -36,7 +40,7 @@ public function execute() $this->_getCheckout()->setShippingItemsInformation($shipToInfo); } } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_redirect('*/*/addresses'); } catch (\Exception $e) { $this->messageManager->addException($e, __('Data saving problem')); diff --git a/app/code/Magento/Multishipping/Controller/Checkout/Overview.php b/app/code/Magento/Multishipping/Controller/Checkout/Overview.php index d97226a393c25..623ced14c2fa9 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/Overview.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/Overview.php @@ -4,14 +4,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Multishipping\Controller\Checkout; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Exception\LocalizedException; +use Magento\Multishipping\Controller\Checkout; use Magento\Multishipping\Model\Checkout\Type\Multishipping\State; use Magento\Payment\Model\Method\AbstractMethod; use Psr\Log\LoggerInterface; -class Overview extends \Magento\Multishipping\Controller\Checkout +class Overview extends Checkout implements HttpPostActionInterface, HttpGetActionInterface { /** * Multishipping checkout place order page @@ -42,7 +47,7 @@ public function execute() $this->_view->loadLayout(); $this->_view->renderLayout(); } catch (LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_redirect('*/*/billing'); } catch (\Exception $e) { $this->_objectManager->get(LoggerInterface::class)->critical($e); diff --git a/app/code/Magento/Multishipping/Controller/Checkout/OverviewPost.php b/app/code/Magento/Multishipping/Controller/Checkout/OverviewPost.php index f05a7f43b8118..762b0f5cca59c 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/OverviewPost.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/OverviewPost.php @@ -3,33 +3,40 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Multishipping\Controller\Checkout; -use Magento\Multishipping\Model\Checkout\Type\Multishipping\State; +use Magento\Checkout\Api\AgreementsValidatorInterface; use Magento\Customer\Api\AccountManagementInterface; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Customer\Model\Session; +use Magento\Framework\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Data\Form\FormKey\Validator; use Magento\Framework\Exception\PaymentException; use Magento\Framework\Session\SessionManagerInterface; +use Magento\Multishipping\Controller\Checkout; +use Magento\Multishipping\Model\Checkout\Type\Multishipping\State; +use Psr\Log\LoggerInterface; /** - * Class OverviewPost - * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class OverviewPost extends \Magento\Multishipping\Controller\Checkout +class OverviewPost extends Checkout implements HttpPostActionInterface { /** - * @var \Magento\Framework\Data\Form\FormKey\Validator + * @var Validator */ protected $formKeyValidator; /** - * @var \Psr\Log\LoggerInterface + * @var LoggerInterface */ protected $logger; /** - * @var \Magento\Checkout\Api\AgreementsValidatorInterface + * @var AgreementsValidatorInterface */ protected $agreementsValidator; @@ -39,23 +46,23 @@ class OverviewPost extends \Magento\Multishipping\Controller\Checkout private $session; /** - * @param \Magento\Framework\App\Action\Context $context - * @param \Magento\Customer\Model\Session $customerSession + * @param Context $context + * @param Session $customerSession * @param CustomerRepositoryInterface $customerRepository * @param AccountManagementInterface $accountManagement - * @param \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator - * @param \Psr\Log\LoggerInterface $logger - * @param \Magento\Checkout\Api\AgreementsValidatorInterface $agreementValidator + * @param Validator $formKeyValidator + * @param LoggerInterface $logger + * @param AgreementsValidatorInterface $agreementValidator * @param SessionManagerInterface $session */ public function __construct( - \Magento\Framework\App\Action\Context $context, - \Magento\Customer\Model\Session $customerSession, + Context $context, + Session $customerSession, CustomerRepositoryInterface $customerRepository, AccountManagementInterface $accountManagement, - \Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator, - \Psr\Log\LoggerInterface $logger, - \Magento\Checkout\Api\AgreementsValidatorInterface $agreementValidator, + Validator $formKeyValidator, + LoggerInterface $logger, + AgreementsValidatorInterface $agreementValidator, SessionManagerInterface $session ) { $this->formKeyValidator = $formKeyValidator; @@ -89,7 +96,7 @@ public function execute() try { if (!$this->agreementsValidator->isValid(array_keys($this->getRequest()->getPost('agreement', [])))) { - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __('Please agree to all Terms and Conditions before placing the order.') ); $this->_redirect('*/*/billing'); @@ -119,7 +126,7 @@ public function execute() } catch (PaymentException $e) { $message = $e->getMessage(); if (!empty($message)) { - $this->messageManager->addError($message); + $this->messageManager->addErrorMessage($message); } $this->_redirect('*/*/billing'); } catch (\Magento\Checkout\Exception $e) { @@ -131,7 +138,7 @@ public function execute() 'multi-shipping' ); $this->_getCheckout()->getCheckoutSession()->clearQuote(); - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_redirect('*/cart'); } catch (\Magento\Framework\Exception\LocalizedException $e) { $this->_objectManager->get( @@ -141,7 +148,7 @@ public function execute() $e->getMessage(), 'multi-shipping' ); - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_redirect('*/*/billing'); } catch (\Exception $e) { $this->logger->critical($e); @@ -156,7 +163,7 @@ public function execute() } catch (\Exception $e) { $this->logger->error($e->getMessage()); } - $this->messageManager->addError(__('Order place error')); + $this->messageManager->addErrorMessage(__('Order place error')); $this->_redirect('*/*/billing'); } } diff --git a/app/code/Magento/Multishipping/Controller/Checkout/ShippingPost.php b/app/code/Magento/Multishipping/Controller/Checkout/ShippingPost.php index 1e1d3dbace623..d8ab9faa24a36 100644 --- a/app/code/Magento/Multishipping/Controller/Checkout/ShippingPost.php +++ b/app/code/Magento/Multishipping/Controller/Checkout/ShippingPost.php @@ -4,13 +4,19 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Multishipping\Controller\Checkout; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Multishipping\Controller\Checkout; use Magento\Multishipping\Model\Checkout\Type\Multishipping\State; -class ShippingPost extends \Magento\Multishipping\Controller\Checkout +class ShippingPost extends Checkout implements HttpPostActionInterface { /** + * Shipping action + * * @return void */ public function execute() @@ -26,7 +32,7 @@ public function execute() $this->_getState()->setCompleteStep(State::STEP_SHIPPING); $this->_redirect('*/*/billing'); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $this->_redirect('*/*/shipping'); } } diff --git a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php index 49212202b5f62..8845395be406e 100644 --- a/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php +++ b/app/code/Magento/Multishipping/Model/Checkout/Type/Multishipping.php @@ -695,7 +695,7 @@ protected function _prepareOrder(\Magento\Quote\Model\Quote\Address $address) ); $shippingMethodCode = $address->getShippingMethod(); - if (isset($shippingMethodCode) && !empty($shippingMethodCode)) { + if ($shippingMethodCode) { $rate = $address->getShippingRateByCode($shippingMethodCode); $shippingPrice = $rate->getPrice(); } else { @@ -975,7 +975,8 @@ public function getMinimumAmountError() \Magento\Store\Model\ScopeInterface::SCOPE_STORE ); } - return $error; + + return __($error); } /** diff --git a/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StorefrontRemoveProductOnCheckoutActionGroup.xml b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StorefrontRemoveProductOnCheckoutActionGroup.xml new file mode 100644 index 0000000000000..af0f3e2d597b8 --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/ActionGroup/StorefrontRemoveProductOnCheckoutActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StorefrontRemoveProductOnCheckoutActionGroup"> + <arguments> + <argument name="itemNumber" type="string" defaultValue="1"/> + </arguments> + + <click selector="{{MultishippingSection.removeItemButton(itemNumber)}}" stepKey="removeItem"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection/MultishippingSection.xml b/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection/MultishippingSection.xml index db037d50f7dc6..9c89ffa3cd405 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection/MultishippingSection.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Section/MultishippingSection/MultishippingSection.xml @@ -14,5 +14,6 @@ <element name="shippingAddressSelector" type="select" selector="//tr[position()={{addressPosition}}]//td[@data-th='Send To']//select" parameterized="true"/> <element name="shippingAddressOptions" type="select" selector="#multiship-addresses-table tbody tr:nth-of-type({{addressPosition}}) .col.address select option:nth-of-type({{optionIndex}})" parameterized="true"/> <element name="selectShippingAddress" type="select" selector="(//table[@id='multiship-addresses-table'] //div[@class='field address'] //select)[{{sequenceNumber}}]" parameterized="true"/> + <element name="removeItemButton" type="button" selector="//a[contains(@title, 'Remove Item')][{{var}}]" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutWithWithVirtualProductTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutWithWithVirtualProductTest.xml new file mode 100644 index 0000000000000..632950120474d --- /dev/null +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontCheckoutWithWithVirtualProductTest.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckoutWithWithVirtualProductTest"> + <annotations> + <features value="Multishipping"/> + <stories value="Multiple Shipping"/> + <title value="Check error when cart contains virtual product"/> + <description value="Check error when cart contains only virtual product"/> + <severity value="MAJOR"/> + <testCaseId value="MC-36921"/> + <group value="Multishipment"/> + </annotations> + <before> + <createData entity="SimpleSubCategory" stepKey="createCategory"/> + <createData entity="SimpleProduct" stepKey="firstProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="VirtualProduct" stepKey="virtualProduct"> + <requiredEntity createDataKey="createCategory"/> + </createData> + <createData entity="Customer_US_UK_DE" stepKey="createCustomerWithMultipleAddresses"/> + </before> + <after> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + <deleteData createDataKey="firstProduct" stepKey="deleteFirstProduct"/> + <deleteData createDataKey="virtualProduct" stepKey="deleteVirtualProduct"/> + <deleteData createDataKey="createCategory" stepKey="deleteCategory"/> + <deleteData createDataKey="createCustomerWithMultipleAddresses" stepKey="deleteCustomer"/> + </after> + <!-- Login to the Storefront as created customer --> + <actionGroup ref="LoginToStorefrontActionGroup" stepKey="loginAsCustomer"> + <argument name="Customer" value="$$createCustomerWithMultipleAddresses$$"/> + </actionGroup> + <!-- Open the simple product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="goToFirstProductPage"> + <argument name="productUrl" value="$$firstProduct.custom_attributes[url_key]$$"/> + </actionGroup> + <!-- Add the simple product to the Shopping Cart --> + <actionGroup ref="AddProductWithQtyToCartFromStorefrontProductPageActionGroup" stepKey="addFirstProductToCart"> + <argument name="productName" value="$$firstProduct.name$$"/> + <argument name="productQty" value="1"/> + </actionGroup> + <!-- Open the virtual product page --> + <actionGroup ref="StorefrontOpenProductPageActionGroup" stepKey="goToVirtualProductPage"> + <argument name="productUrl" value="$$virtualProduct.custom_attributes[url_key]$$"/> + </actionGroup> + <!-- Add the virtual product to the Shopping Cart --> + <actionGroup ref="AddProductWithQtyToCartFromStorefrontProductPageActionGroup" stepKey="addVirtualProductToCart"> + <argument name="productName" value="$$virtualProduct.name$$"/> + <argument name="productQty" value="1"/> + </actionGroup> + <!-- Go to Cart --> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <!-- Check Out with Multiple Addresses --> + <actionGroup ref="StorefrontCheckoutWithMultipleAddressesActionGroup" stepKey="checkoutWithMultipleAddresses"/> + <!-- Remove simple product from cart --> + <actionGroup ref="StorefrontRemoveProductOnCheckoutActionGroup" stepKey="removeFirstProductFromCart"/> + <!-- Assert error message on checkout --> + <actionGroup ref="StorefrontAssertCheckoutErrorMessageActionGroup" stepKey="assertErrorMessage"> + <argument name="message" value="The current cart does not match multi shipping criteria, please review or contact the store administrator"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml index 494259e0ead9d..fe33078755ac4 100644 --- a/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml +++ b/app/code/Magento/Multishipping/Test/Mftf/Test/StorefrontVerifySecureURLRedirectMultishippingTest.xml @@ -33,8 +33,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$category.name$$)}}" stepKey="goToCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="moveMouseOverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="clickAddToCartButton"/> - <waitForPageLoad stepKey="waitForAddToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="clickAddToCartButton"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForAddedToCartSuccessMessage"/> <see selector="{{StorefrontCategoryMainSection.SuccessMsg}}" userInput="You added $$product.name$$ to your shopping cart." stepKey="seeAddedToCartSuccessMessage"/> <see selector="{{StorefrontMinicartSection.quantity}}" userInput="1" stepKey="seeCartQuantity"/> diff --git a/app/code/Magento/Multishipping/etc/config.xml b/app/code/Magento/Multishipping/etc/config.xml index aee4199ed4757..0df8742016aed 100644 --- a/app/code/Magento/Multishipping/etc/config.xml +++ b/app/code/Magento/Multishipping/etc/config.xml @@ -13,5 +13,10 @@ <checkout_multiple_maximum_qty>100</checkout_multiple_maximum_qty> </options> </multishipping> + <sales> + <minimum_order> + <multi_address_error_message>The current cart does not match multi shipping criteria, please review or contact the store administrator</multi_address_error_message> + </minimum_order> + </sales> </default> </config> diff --git a/app/code/Magento/Multishipping/i18n/en_US.csv b/app/code/Magento/Multishipping/i18n/en_US.csv index f9ab587c65fa3..430b16b8cc237 100644 --- a/app/code/Magento/Multishipping/i18n/en_US.csv +++ b/app/code/Magento/Multishipping/i18n/en_US.csv @@ -87,8 +87,9 @@ Options,Options "Maximum Qty Allowed for Shipping to Multiple Addresses","Maximum Qty Allowed for Shipping to Multiple Addresses" "Review Order","Review Order" "Select Shipping Method","Select Shipping Method" -"We received your order!","We received your order!" +"The order was not successful!","The order was not successful!" "Ship to:","Ship to:" "Error:","Error:" "We are unable to process your request. Please, try again later.","We are unable to process your request. Please, try again later." "Quote address for failed order ID "%1" not found.","Quote address for failed order ID "%1" not found." +"The current cart does not match multi shipping criteria, please review or contact the store administrator","The current cart does not match multi shipping criteria, please review or contact the store administrator" diff --git a/app/code/Magento/NewRelicReporting/Model/Observer/CheckConfig.php b/app/code/Magento/NewRelicReporting/Model/Observer/CheckConfig.php index f7eb658f40784..00060c8f94ca5 100644 --- a/app/code/Magento/NewRelicReporting/Model/Observer/CheckConfig.php +++ b/app/code/Magento/NewRelicReporting/Model/Observer/CheckConfig.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\NewRelicReporting\Model\Observer; use Magento\Framework\Event\Observer; @@ -11,9 +13,6 @@ use Magento\Framework\Message\ManagerInterface; use Magento\NewRelicReporting\Model\NewRelicWrapper; -/** - * Class CheckConfig - */ class CheckConfig implements ObserverInterface { /** @@ -58,9 +57,9 @@ public function execute(Observer $observer) if ($this->config->isNewRelicEnabled()) { if (!$this->newRelicWrapper->isExtensionInstalled()) { $this->config->disableModule(); - $this->messageManager->addError( + $this->messageManager->addErrorMessage( __( - 'The New Relic integration requires the newrelic-php5 agent, which is not installed. More + 'The New Relic integration requires the newrelic-php5 agent, which is not installed. More information on installing the agent is available <a target="_blank" href="%1">here</a>.', 'https://docs.newrelic.com/docs/agents/php-agent/installation/php-agent-installation-overview' ), diff --git a/app/code/Magento/NewRelicReporting/Test/Unit/Model/Observer/CheckConfigTest.php b/app/code/Magento/NewRelicReporting/Test/Unit/Model/Observer/CheckConfigTest.php index 6bb1ee7214609..f64acdc71d2a8 100644 --- a/app/code/Magento/NewRelicReporting/Test/Unit/Model/Observer/CheckConfigTest.php +++ b/app/code/Magento/NewRelicReporting/Test/Unit/Model/Observer/CheckConfigTest.php @@ -125,7 +125,7 @@ public function testCheckConfig() $this->config->expects($this->once()) ->method('disableModule'); $this->messageManager->expects($this->once()) - ->method('addError'); + ->method('addErrorMessage'); $this->model->execute($eventObserver); } diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Save.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Save.php index 2dbe10bf1bdc9..4f93e3e4f73a3 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Save.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Queue/Save.php @@ -88,7 +88,7 @@ public function execute() $this->_redirect('*/*'); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); $id = $this->getRequest()->getParam('id'); if ($id) { $this->_redirect('*/*/edit', ['id' => $id]); diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php index cdef44b2da757..e8ec57f7a153e 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassDelete.php @@ -46,7 +46,7 @@ public function execute() { $subscribersIds = $this->getRequest()->getParam('subscriber'); if (!is_array($subscribersIds)) { - $this->messageManager->addError(__('Please select one or more subscribers.')); + $this->messageManager->addErrorMessage(__('Please select one or more subscribers.')); } else { try { foreach ($subscribersIds as $subscriberId) { @@ -57,7 +57,7 @@ public function execute() } $this->messageManager->addSuccess(__('Total of %1 record(s) were deleted.', count($subscribersIds))); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassUnsubscribe.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassUnsubscribe.php index b61494f795905..5e0890215c815 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassUnsubscribe.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Subscriber/MassUnsubscribe.php @@ -4,21 +4,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Newsletter\Controller\Adminhtml\Subscriber; -use Magento\Newsletter\Controller\Adminhtml\Subscriber; use Magento\Backend\App\Action\Context; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\App\ObjectManager; use Magento\Framework\App\Response\Http\FileFactory; +use Magento\Newsletter\Controller\Adminhtml\Subscriber; use Magento\Newsletter\Model\SubscriberFactory; -use Magento\Framework\App\ObjectManager; -class MassUnsubscribe extends Subscriber +class MassUnsubscribe extends Subscriber implements HttpPostActionInterface { /** * @var SubscriberFactory */ private $subscriberFactory; - + /** * @param Context $context * @param FileFactory $fileFactory @@ -32,17 +35,17 @@ public function __construct( $this->subscriberFactory = $subscriberFactory ?: ObjectManager::getInstance()->get(SubscriberFactory::class); parent::__construct($context, $fileFactory); } - + /** * Unsubscribe one or more subscribers action * * @return void */ - public function execute() + public function execute(): void { $subscribersIds = $this->getRequest()->getParam('subscriber'); if (!is_array($subscribersIds)) { - $this->messageManager->addError(__('Please select one or more subscribers.')); + $this->messageManager->addErrorMessage(__('Please select one or more subscribers.')); } else { try { foreach ($subscribersIds as $subscriberId) { @@ -53,7 +56,7 @@ public function execute() } $this->messageManager->addSuccess(__('A total of %1 record(s) were updated.', count($subscribersIds))); } catch (\Exception $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } } diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Delete.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Delete.php index d327d44feceb8..c8352a028fe2d 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Delete.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Delete.php @@ -4,9 +4,15 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Newsletter\Controller\Adminhtml\Template; -class Delete extends \Magento\Newsletter\Controller\Adminhtml\Template +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Newsletter\Controller\Adminhtml\Template; + +class Delete extends Template implements HttpGetActionInterface, HttpPostActionInterface { /** * Delete newsletter Template @@ -26,7 +32,7 @@ public function execute() $this->messageManager->addSuccess(__('The newsletter template has been deleted.')); $this->_getSession()->setFormData(false); } catch (\Magento\Framework\Exception\LocalizedException $e) { - $this->messageManager->addError($e->getMessage()); + $this->messageManager->addErrorMessage($e->getMessage()); } catch (\Exception $e) { $this->messageManager->addException($e, __('We can\'t delete this template right now.')); } diff --git a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Save.php b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Save.php index dc4d50c22b162..cf4aad2059a01 100644 --- a/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Save.php +++ b/app/code/Magento/Newsletter/Controller/Adminhtml/Template/Save.php @@ -69,7 +69,7 @@ public function execute() $this->_redirect('*/template'); return; } catch (LocalizedException $e) { - $this->messageManager->addError(nl2br($e->getMessage())); + $this->messageManager->addErrorMessage(nl2br($e->getMessage())); $this->_getSession()->setData('newsletter_template_form_data', $this->getRequest()->getParams()); } catch (\Exception $e) { $this->messageManager->addException($e, __('Something went wrong while saving this template.')); diff --git a/app/code/Magento/Newsletter/Controller/Manage/Save.php b/app/code/Magento/Newsletter/Controller/Manage/Save.php index 01012e39a992a..b70c84a6d1099 100644 --- a/app/code/Magento/Newsletter/Controller/Manage/Save.php +++ b/app/code/Magento/Newsletter/Controller/Manage/Save.php @@ -77,7 +77,7 @@ public function execute() $customerId = $this->_customerSession->getCustomerId(); if ($customerId === null) { - $this->messageManager->addError(__('Something went wrong while saving your subscription.')); + $this->messageManager->addErrorMessage(__('Something went wrong while saving your subscription.')); } else { try { $customer = $this->customerRepository->getById($customerId); @@ -105,7 +105,7 @@ public function execute() $this->messageManager->addSuccess(__('We have updated your subscription.')); } } catch (\Exception $e) { - $this->messageManager->addError(__('Something went wrong while saving your subscription.')); + $this->messageManager->addErrorMessage(__('Something went wrong while saving your subscription.')); } } return $this->_redirect('customer/account/'); diff --git a/app/code/Magento/Newsletter/Model/SubscriptionManager.php b/app/code/Magento/Newsletter/Model/SubscriptionManager.php index 846d095625e0c..57c6cd8b843a7 100644 --- a/app/code/Magento/Newsletter/Model/SubscriptionManager.php +++ b/app/code/Magento/Newsletter/Model/SubscriptionManager.php @@ -195,12 +195,14 @@ private function saveSubscriber( ): bool { $statusChanged = (int)$subscriber->getStatus() !== $status; $emailChanged = $subscriber->getEmail() !== $customer->getEmail(); - if ($subscriber->getId() - && !$statusChanged - && (int)$subscriber->getCustomerId() === (int)$customer->getId() - && (int)$subscriber->getStoreId() === $storeId - && !$emailChanged - ) { + if ($this->dontNeedToSaveSubscriber( + $subscriber, + $customer, + $statusChanged, + $storeId, + $status, + $emailChanged + )) { return false; } @@ -220,10 +222,37 @@ private function saveSubscriber( /** * If the subscriber is waiting to confirm from the customer - * and customer changed the email + * or customer changed the email * than need to send confirmation letter to the new email */ - return $status === Subscriber::STATUS_NOT_ACTIVE && $emailChanged; + return $status === Subscriber::STATUS_NOT_ACTIVE || $emailChanged; + } + + /** + * Don't need to save subscriber model + * + * @param Subscriber $subscriber + * @param CustomerInterface $customer + * @param bool $statusChanged + * @param int $storeId + * @param int $status + * @param bool $emailChanged + * @return bool + */ + private function dontNeedToSaveSubscriber( + Subscriber $subscriber, + CustomerInterface $customer, + bool $statusChanged, + int $storeId, + int $status, + bool $emailChanged + ): bool { + return $subscriber->getId() + && !$statusChanged + && (int)$subscriber->getCustomerId() === (int)$customer->getId() + && (int)$subscriber->getStoreId() === $storeId + && !$emailChanged + && $status !== Subscriber::STATUS_NOT_ACTIVE; } /** diff --git a/app/code/Magento/Newsletter/Test/Unit/Controller/Manage/SaveTest.php b/app/code/Magento/Newsletter/Test/Unit/Controller/Manage/SaveTest.php index f333467732e30..e5b09c2e89852 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Controller/Manage/SaveTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Controller/Manage/SaveTest.php @@ -122,7 +122,7 @@ public function testSaveActionInvalidFormKey() $this->messageManagerMock->expects($this->never()) ->method('addSuccess'); $this->messageManagerMock->expects($this->never()) - ->method('addError'); + ->method('addErrorMessage'); $this->action->execute(); } @@ -140,7 +140,7 @@ public function testSaveActionNoCustomerInSession() $this->messageManagerMock->expects($this->never()) ->method('addSuccess'); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('Something went wrong while saving your subscription.'); $this->action->execute(); } @@ -169,7 +169,7 @@ public function testSaveActionWithException() $this->messageManagerMock->expects($this->never()) ->method('addSuccess'); $this->messageManagerMock->expects($this->once()) - ->method('addError') + ->method('addErrorMessage') ->with('Something went wrong while saving your subscription.'); $this->action->execute(); } diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriptionManagerTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriptionManagerTest.php index 4e1f18a26a95a..6139d86191f44 100644 --- a/app/code/Magento/Newsletter/Test/Unit/Model/SubscriptionManagerTest.php +++ b/app/code/Magento/Newsletter/Test/Unit/Model/SubscriptionManagerTest.php @@ -454,7 +454,7 @@ public function subscribeCustomerDataProvider(): array 'subscriber_status' => Subscriber::STATUS_SUBSCRIBED, 'subscriber_confirm_code' => '', ], - 'needToSendEmail' => false, + 'needToSendEmail' => true, ], 'Update subscription data: subscription confirm required ' => [ 'subscriber_data' => [ @@ -618,7 +618,7 @@ public function unsubscribeCustomerDataProvider(): array 'subscriber_status' => Subscriber::STATUS_NOT_ACTIVE, 'subscriber_confirm_code' => '', ], - 'needToSendEmail' => false, + 'needToSendEmail' => true, ], 'Update subscription data' => [ 'subscriber_data' => [ @@ -642,7 +642,7 @@ public function unsubscribeCustomerDataProvider(): array 'subscriber_status' => Subscriber::STATUS_UNSUBSCRIBED, 'subscriber_confirm_code' => '', ], - 'needToSendEmail' => false, + 'needToSendEmail' => true, ], ]; } diff --git a/app/code/Magento/PageCache/Plugin/AppendNoStoreCacheHeader.php b/app/code/Magento/PageCache/Plugin/AppendNoStoreCacheHeader.php new file mode 100644 index 0000000000000..fc18855a51710 --- /dev/null +++ b/app/code/Magento/PageCache/Plugin/AppendNoStoreCacheHeader.php @@ -0,0 +1,30 @@ +<?php +/** + * + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\PageCache\Plugin; + +use Magento\Framework\App\FrontControllerInterface; +use Magento\Framework\App\Response\HttpInterface; + +class AppendNoStoreCacheHeader +{ + /** + * Set cache-control header + * + * @param FrontControllerInterface $controller + * @param HttpInterface $response + * @return HttpInterface + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function afterDispatch(FrontControllerInterface $controller, HttpInterface $response): HttpInterface + { + $response->setHeader('Cache-Control', 'no-store'); + return $response; + } +} diff --git a/app/code/Magento/PageCache/etc/webapi_rest/di.xml b/app/code/Magento/PageCache/etc/webapi_rest/di.xml new file mode 100644 index 0000000000000..04906a615a9df --- /dev/null +++ b/app/code/Magento/PageCache/etc/webapi_rest/di.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Framework\App\FrontControllerInterface"> + <plugin name="append_no_store_cache_header" type="Magento\PageCache\Plugin\AppendNoStoreCacheHeader" /> + </type> +</config> diff --git a/app/code/Magento/PageCache/etc/webapi_soap/di.xml b/app/code/Magento/PageCache/etc/webapi_soap/di.xml new file mode 100644 index 0000000000000..04906a615a9df --- /dev/null +++ b/app/code/Magento/PageCache/etc/webapi_soap/di.xml @@ -0,0 +1,12 @@ +<?xml version="1.0"?> +<!-- +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> + <type name="Magento\Framework\App\FrontControllerInterface"> + <plugin name="append_no_store_cache_header" type="Magento\PageCache\Plugin\AppendNoStoreCacheHeader" /> + </type> +</config> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml index 898c26bb4b45a..cea228ac7a344 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonInCheckoutPageTest.xml @@ -16,6 +16,9 @@ <description value="Users are able to place order using Paypal Smart Button on Checkout Page, payment action is Sale"/> <severity value="CRITICAL"/> <testCaseId value="MC-13690"/> + <skip> + <issueId value="MC-37236"/> + </skip> <group value="paypalExpress"/> </annotations> <before> diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml index 3fd5f44d5a4b6..a4d99ecbf7e61 100644 --- a/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml +++ b/app/code/Magento/Paypal/Test/Mftf/Test/StorefrontPaypalSmartButtonWithFranceMerchantCountryTest.xml @@ -16,6 +16,9 @@ <description value="Users are able to place order using Paypal Smart Button using Euro currency and merchant country is France"/> <severity value="MAJOR"/> <testCaseId value="MC-33274"/> + <skip> + <issueId value="MC-37236"/> + </skip> <group value="paypalExpress"/> </annotations> <before> diff --git a/app/code/Magento/Persistent/Model/Customer/Authorization.php b/app/code/Magento/Persistent/Model/Customer/Authorization.php new file mode 100644 index 0000000000000..6d8859a30fd96 --- /dev/null +++ b/app/code/Magento/Persistent/Model/Customer/Authorization.php @@ -0,0 +1,58 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Persistent\Model\Customer; + +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Framework\AuthorizationInterface; +use Magento\Persistent\Helper\Session as PersistentSession; + +/** + * Authorization logic for persistent customers + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +class Authorization implements AuthorizationInterface +{ + /** + * @var CustomerSession + */ + private $customerSession; + + /** + * @var PersistentSession + */ + private $persistentSession; + + /** + * @param CustomerSession $customerSession + * @param PersistentSession $persistentSession + */ + public function __construct( + CustomerSession $customerSession, + PersistentSession $persistentSession + ) { + $this->customerSession = $customerSession; + $this->persistentSession = $persistentSession; + } + + /** + * @inheritdoc + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function isAllowed( + $resource, + $privilege = null + ) { + if ($this->persistentSession->isPersistent() && !$this->customerSession->isLoggedIn()) { + return false; + } + + return true; + } +} diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/GuestCheckoutWithEnabledPersistentTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/GuestCheckoutWithEnabledPersistentTest.xml index 43390598f7cb3..4f68c055f2615 100644 --- a/app/code/Magento/Persistent/Test/Mftf/Test/GuestCheckoutWithEnabledPersistentTest.xml +++ b/app/code/Magento/Persistent/Test/Mftf/Test/GuestCheckoutWithEnabledPersistentTest.xml @@ -48,8 +48,7 @@ <waitForLoadingMaskToDisappear stepKey="waitForLoadingMask"/> <click selector="{{CheckoutShippingGuestInfoSection.firstShippingMethod}}" stepKey="selectFirstShippingMethod"/> <!-- Check that have the same values after page reload --> - <amOnPage url="{{CheckoutPage.url}}" stepKey="amOnCheckoutShippingInfoPage"/> - <waitForPageLoad stepKey="waitForShippingPageReload"/> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="amOnCheckoutShippingInfoPage"/> <seeInField selector="{{CheckoutShippingGuestInfoSection.email}}" userInput="{{CustomerEntityOne.email}}" stepKey="seeEmailOnCheckout" /> <seeInField selector="{{CheckoutShippingGuestInfoSection.firstName}}" userInput="{{CustomerEntityOne.firstName}}" stepKey="seeFirstnameOnCheckout" /> <seeInField selector="{{CheckoutShippingGuestInfoSection.lastName}}" userInput="{{CustomerEntityOne.lastName}}" stepKey="seeLastnameOnCheckout" /> diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml index b41cad61c93a5..45ccab54de5f3 100644 --- a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml +++ b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontCorrectWelcomeMessageAfterCustomerIsLoggedOutTest.xml @@ -54,7 +54,7 @@ stepKey="seeLoggedInCustomerWelcomeMessage"/> <!--Logout and check default welcome message--> <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="storefrontCustomerLogout"/> - <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" stepKey="seeCustomerSignOutPageUrl"/> + <actionGroup ref="AssertStorefrontCustomerLogoutSuccessPageActionGroup" stepKey="seeCustomerSignOutPageUrl"/> <see userInput="Default welcome msg!" selector="{{StorefrontHeaderSection.welcomeMessage}}" stepKey="seeDefaultWelcomeMessage"/> @@ -71,7 +71,7 @@ <!--Logout and check persistent customer welcome message--> <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="storefrontCustomerLogout1"/> - <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" stepKey="seeCustomerSignOutPageUrl1"/> + <actionGroup ref="AssertStorefrontCustomerLogoutSuccessPageActionGroup" stepKey="seeCustomerSignOutPageUrl1"/> <see userInput="Welcome, $$createCustomerForPersistent.firstname$$ $$createCustomerForPersistent.lastname$$! Not you?" selector="{{StorefrontHeaderSection.welcomeMessage}}" stepKey="seePersistentWelcomeMessage"/> diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyShoppingCartPersistenceUnderLongTermCookieTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyShoppingCartPersistenceUnderLongTermCookieTest.xml index 80ca7a2eb90c7..b3bf4e4dd009f 100644 --- a/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyShoppingCartPersistenceUnderLongTermCookieTest.xml +++ b/app/code/Magento/Persistent/Test/Mftf/Test/StorefrontVerifyShoppingCartPersistenceUnderLongTermCookieTest.xml @@ -73,7 +73,7 @@ <!-- 4. Click Sign Out --> <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="logoutJohnSmithCustomer"/> - <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" stepKey="seeLogoutSuccessPageUrlAfterLogOutJohnSmithCustomer"/> + <actionGroup ref="AssertStorefrontCustomerLogoutSuccessPageActionGroup" stepKey="seeLogoutSuccessPageUrlAfterLogOutJohnSmithCustomer"/> <waitForPageLoad stepKey="waitForRedirectToHomePage"/> <waitForText selector="{{StorefrontCMSPageSection.mainContent}}" userInput="CMS homepage content goes here." stepKey="waitForLoadContentMessage"/> <actionGroup ref="StorefrontAssertPersistentCustomerWelcomeMessageNotPresentActionGroup" stepKey="dontSeeWelcomeJohnSmithCustomerNotYouMessage"> @@ -102,7 +102,7 @@ <!-- 7. Click Log Out --> <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="logoutJohnDoeCustomer"/> - <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" stepKey="seeLogoutSuccessPageUrlAfterLogOutJohnDoeCustomer"/> + <actionGroup ref="AssertStorefrontCustomerLogoutSuccessPageActionGroup" stepKey="seeLogoutSuccessPageUrlAfterLogOutJohnDoeCustomer"/> <actionGroup ref="StorefrontAssertPersistentCustomerWelcomeMessageActionGroup" stepKey="seeWelcomeForJohnDoeCustomer"> <argument name="customerFullName" value="{{Simple_Customer_Without_Address.fullname}}"/> </actionGroup> @@ -149,7 +149,7 @@ <!-- 12. Sign out and click the Not you? link --> <actionGroup ref="CustomerLogoutStorefrontByMenuItemsActionGroup" stepKey="logoutJohnDoeCustomerSecondTime"/> - <seeInCurrentUrl url="{{StorefrontCustomerLogoutSuccessPage.url}}" stepKey="seeLogoutSuccessPageUrlAfterLogOutJohnSmithCustomerSecondTime"/> + <actionGroup ref="AssertStorefrontCustomerLogoutSuccessPageActionGroup" stepKey="seeLogoutSuccessPageUrlAfterLogOutJohnSmithCustomerSecondTime"/> <waitForPageLoad stepKey="waitForHomePageLoadAfter5Seconds"/> <waitForText selector="{{StorefrontCMSPageSection.mainContent}}" userInput="CMS homepage content goes here." stepKey="waitForLoadMainContentMessageOnHomePage"/> <click selector="{{StorefrontPanelHeaderSection.notYouLink}}" stepKey="clickOnNotYouLink" /> diff --git a/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php b/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php new file mode 100644 index 0000000000000..d2abafc7e5ecf --- /dev/null +++ b/app/code/Magento/Persistent/Test/Unit/Model/Customer/AuthorizationTest.php @@ -0,0 +1,112 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Persistent\Test\Unit\Model\Customer; + +use Magento\Customer\Model\Session as CustomerSession; +use Magento\Persistent\Helper\Session as PersistentSession; +use Magento\Persistent\Model\Customer\Authorization as PersistentAuthorization; +use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Magento\Customer\Model\Customer\AuthorizationComposite as CustomerAuthorizationComposite; + +/** + * A test class for the persistent customers authorization + * + * Unit tests for \Magento\Persistent\Model\Customer\Authorization class. + */ +class AuthorizationTest extends TestCase +{ + /** + * @var PersistentSession|MockObject + */ + private $persistentSessionMock; + + /** + * @var PersistentAuthorization + */ + private $persistentCustomerAuthorization; + + /** + * @var CustomerSession|MockObject + */ + private $customerSessionMock; + + /** + * @var CustomerAuthorizationComposite + */ + private $customerAuthorizationComposite; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->persistentSessionMock = $this->getMockBuilder(PersistentSession::class) + ->onlyMethods(['isPersistent']) + ->disableOriginalConstructor() + ->getMock(); + + $this->customerSessionMock = $this->getMockBuilder(CustomerSession::class) + ->onlyMethods(['isLoggedIn']) + ->disableOriginalConstructor() + ->getMock(); + + $this->persistentCustomerAuthorization = new PersistentAuthorization( + $this->customerSessionMock, + $this->persistentSessionMock + ); + + $this->customerAuthorizationComposite = new CustomerAuthorizationComposite( + [$this->persistentCustomerAuthorization] + ); + } + + /** + * Validate if isAuthorized() will return proper permission value for logged in/ out persistent customers + * + * @dataProvider persistentLoggedInCombinations + * @param bool $isPersistent + * @param bool $isLoggedIn + * @param bool $isAllowedExpectation + */ + public function testIsAuthorized( + bool $isPersistent, + bool $isLoggedIn, + bool $isAllowedExpectation + ): void { + $this->persistentSessionMock->method('isPersistent')->willReturn($isPersistent); + $this->customerSessionMock->method('isLoggedIn')->willReturn($isLoggedIn); + $isAllowedResult = $this->customerAuthorizationComposite->isAllowed('self'); + + $this->assertEquals($isAllowedExpectation, $isAllowedResult); + } + + /** + * @return array + */ + public function persistentLoggedInCombinations(): array + { + return [ + [ + true, + false, + false + ], + [ + true, + true, + true + ], + [ + false, + false, + true + ], + ]; + } +} diff --git a/app/code/Magento/Persistent/etc/di.xml b/app/code/Magento/Persistent/etc/di.xml index f49d4361acb52..fd1c97fae66d9 100644 --- a/app/code/Magento/Persistent/etc/di.xml +++ b/app/code/Magento/Persistent/etc/di.xml @@ -12,4 +12,14 @@ <type name="Magento\Customer\CustomerData\Customer"> <plugin name="section_data" type="Magento\Persistent\Model\Plugin\CustomerData" /> </type> + <type name="Magento\Persistent\Model\Customer\Authorization"> + <arguments> + <argument name="customerSession" xsi:type="object">Magento\Customer\Model\Session\Proxy</argument> + </arguments> + </type> + <type name="Magento\Persistent\Helper\Session"> + <arguments> + <argument name="checkoutSession" xsi:type="object">Magento\Checkout\Model\Session\Proxy</argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/webapi_rest/di.xml b/app/code/Magento/Persistent/etc/webapi_rest/di.xml index cb0aec6b460af..89504f0471788 100644 --- a/app/code/Magento/Persistent/etc/webapi_rest/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_rest/di.xml @@ -13,4 +13,11 @@ <plugin name="persistent_convert_customer_cart_to_guest_cart" type="Magento\Persistent\Model\Checkout\GuestShippingInformationManagementPlugin"/> </type> + <type name="Magento\Customer\Model\Customer\AuthorizationComposite"> + <arguments> + <argument name="authorizationChecks" xsi:type="array"> + <item name="persistent_rest_customer_authorization" xsi:type="object">Magento\Persistent\Model\Customer\Authorization</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Persistent/etc/webapi_soap/di.xml b/app/code/Magento/Persistent/etc/webapi_soap/di.xml index cb0aec6b460af..2a440fff03598 100644 --- a/app/code/Magento/Persistent/etc/webapi_soap/di.xml +++ b/app/code/Magento/Persistent/etc/webapi_soap/di.xml @@ -13,4 +13,11 @@ <plugin name="persistent_convert_customer_cart_to_guest_cart" type="Magento\Persistent\Model\Checkout\GuestShippingInformationManagementPlugin"/> </type> + <type name="Magento\Customer\Model\Customer\AuthorizationComposite"> + <arguments> + <argument name="authorizationChecks" xsi:type="array"> + <item name="persistent_soap_customer_authorization" xsi:type="object">Magento\Persistent\Model\Customer\Authorization</item> + </argument> + </arguments> + </type> </config> diff --git a/app/code/Magento/Quote/Model/ChangeQuoteControl.php b/app/code/Magento/Quote/Model/ChangeQuoteControl.php index b88898a816d66..92e25ca6c7d3a 100644 --- a/app/code/Magento/Quote/Model/ChangeQuoteControl.php +++ b/app/code/Magento/Quote/Model/ChangeQuoteControl.php @@ -3,7 +3,6 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ - declare(strict_types=1); namespace Magento\Quote\Model; @@ -12,13 +11,10 @@ use Magento\Quote\Api\ChangeQuoteControlInterface; use Magento\Quote\Api\Data\CartInterface; -/** - * {@inheritdoc} - */ class ChangeQuoteControl implements ChangeQuoteControlInterface { /** - * @var UserContextInterface $userContext + * @var UserContextInterface */ private $userContext; @@ -31,25 +27,20 @@ public function __construct(UserContextInterface $userContext) } /** - * {@inheritdoc} + * @inheritdoc */ public function isAllowed(CartInterface $quote): bool { switch ($this->userContext->getUserType()) { case UserContextInterface::USER_TYPE_CUSTOMER: - $isAllowed = ($quote->getCustomerId() == $this->userContext->getUserId()); - break; + return ($quote->getCustomerId() == $this->userContext->getUserId()); case UserContextInterface::USER_TYPE_GUEST: - $isAllowed = ($quote->getCustomerId() === null); - break; + return ($quote->getCustomerId() === null); case UserContextInterface::USER_TYPE_ADMIN: case UserContextInterface::USER_TYPE_INTEGRATION: - $isAllowed = true; - break; - default: - $isAllowed = false; + return true; } - return $isAllowed; + return false; } } diff --git a/app/code/Magento/Quote/Model/QuoteRepository/Plugin/AccessChangeQuoteControl.php b/app/code/Magento/Quote/Model/QuoteRepository/Plugin/AccessChangeQuoteControl.php index 79b518fc54534..eda0e9638cc0d 100644 --- a/app/code/Magento/Quote/Model/QuoteRepository/Plugin/AccessChangeQuoteControl.php +++ b/app/code/Magento/Quote/Model/QuoteRepository/Plugin/AccessChangeQuoteControl.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Quote\Model\QuoteRepository\Plugin; @@ -32,16 +33,17 @@ public function __construct(ChangeQuoteControlInterface $changeQuoteControl) /** * Checks if change quote's customer id is allowed for current user. * + * A StateException is thrown if Guest's or Customer's customer_id not match user_id or unknown user type + * * @param CartRepositoryInterface $subject * @param CartInterface $quote - * @throws StateException if Guest has customer_id or Customer's customer_id not much with user_id - * or unknown user's type * @return void + * @throws StateException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function beforeSave(CartRepositoryInterface $subject, CartInterface $quote) + public function beforeSave(CartRepositoryInterface $subject, CartInterface $quote): void { - if (! $this->changeQuoteControl->isAllowed($quote)) { + if (!$this->changeQuoteControl->isAllowed($quote)) { throw new StateException(__("Invalid state change requested")); } } diff --git a/app/code/Magento/Quote/Test/Unit/Model/ChangeQuoteControlTest.php b/app/code/Magento/Quote/Test/Unit/Model/ChangeQuoteControlTest.php index f302372344c11..a467f3e25d698 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/ChangeQuoteControlTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/ChangeQuoteControlTest.php @@ -16,130 +16,94 @@ /** * Unit test for \Magento\Quote\Model\ChangeQuoteControl - * - * Class \Magento\Quote\Test\Unit\Model\ChangeQuoteControlTest */ class ChangeQuoteControlTest extends TestCase { - /** - * @var ObjectManager - */ - protected $objectManager; - /** * @var ChangeQuoteControl */ protected $model; /** - * @var MockObject + * @var MockObject|UserContextInterface */ protected $userContextMock; /** - * @var MockObject + * @var MockObject|CartInterface */ protected $quoteMock; protected function setUp(): void { - $this->objectManager = new ObjectManager($this); $this->userContextMock = $this->getMockForAbstractClass(UserContextInterface::class); - $this->model = $this->objectManager->getObject( - ChangeQuoteControl::class, - [ - 'userContext' => $this->userContextMock - ] - ); - - $this->quoteMock = $this->getMockForAbstractClass( - CartInterface::class, - [], - '', - false, - true, - true, - ['getCustomerId'] - ); + $this->model = new ChangeQuoteControl($this->userContextMock); + + $this->quoteMock = $this->getMockBuilder(CartInterface::class) + ->disableOriginalConstructor() + ->addMethods(['getCustomerId']) + ->getMockForAbstractClass(); } - /** - * Test if the quote is belonged to customer - */ public function testIsAllowedIfTheQuoteIsBelongedToCustomer() { $quoteCustomerId = 1; - $this->quoteMock->expects($this->any())->method('getCustomerId') + $this->quoteMock->method('getCustomerId') ->willReturn($quoteCustomerId); - $this->userContextMock->expects($this->any())->method('getUserType') + $this->userContextMock->method('getUserType') ->willReturn(UserContextInterface::USER_TYPE_CUSTOMER); - $this->userContextMock->expects($this->any())->method('getUserId') + $this->userContextMock->method('getUserId') ->willReturn($quoteCustomerId); $this->assertTrue($this->model->isAllowed($this->quoteMock)); } - /** - * Test if the quote is not belonged to customer - */ public function testIsAllowedIfTheQuoteIsNotBelongedToCustomer() { $currentCustomerId = 1; $quoteCustomerId = 2; - $this->quoteMock->expects($this->any())->method('getCustomerId') + $this->quoteMock->method('getCustomerId') ->willReturn($quoteCustomerId); - $this->userContextMock->expects($this->any())->method('getUserType') + $this->userContextMock->method('getUserType') ->willReturn(UserContextInterface::USER_TYPE_CUSTOMER); - $this->userContextMock->expects($this->any())->method('getUserId') + $this->userContextMock->method('getUserId') ->willReturn($currentCustomerId); $this->assertFalse($this->model->isAllowed($this->quoteMock)); } - /** - * Test if the quote is belonged to guest and the context is guest - */ public function testIsAllowedIfQuoteIsBelongedToGuestAndContextIsGuest() { $quoteCustomerId = null; - $this->quoteMock->expects($this->any())->method('getCustomerId') + $this->quoteMock->method('getCustomerId') ->willReturn($quoteCustomerId); - $this->userContextMock->expects($this->any())->method('getUserType') + $this->userContextMock->method('getUserType') ->willReturn(UserContextInterface::USER_TYPE_GUEST); $this->assertTrue($this->model->isAllowed($this->quoteMock)); } - /** - * Test if the quote is belonged to customer and the context is guest - */ public function testIsAllowedIfQuoteIsBelongedToCustomerAndContextIsGuest() { $quoteCustomerId = 1; - $this->quoteMock->expects($this->any())->method('getCustomerId') + $this->quoteMock->method('getCustomerId') ->willReturn($quoteCustomerId); - $this->userContextMock->expects($this->any())->method('getUserType') + $this->userContextMock->method('getUserType') ->willReturn(UserContextInterface::USER_TYPE_GUEST); $this->assertFalse($this->model->isAllowed($this->quoteMock)); } - /** - * Test if the context is admin - */ public function testIsAllowedIfContextIsAdmin() { - $this->userContextMock->expects($this->any())->method('getUserType') + $this->userContextMock->method('getUserType') ->willReturn(UserContextInterface::USER_TYPE_ADMIN); $this->assertTrue($this->model->isAllowed($this->quoteMock)); } - /** - * Test if the context is integration - */ public function testIsAllowedIfContextIsIntegration() { - $this->userContextMock->expects($this->any())->method('getUserType') + $this->userContextMock->method('getUserType') ->willReturn(UserContextInterface::USER_TYPE_INTEGRATION); $this->assertTrue($this->model->isAllowed($this->quoteMock)); } diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteRepository/Plugin/AccessChangeQuoteControlTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteRepository/Plugin/AccessChangeQuoteControlTest.php index 85098d2f23448..199ddfd9b9120 100644 --- a/app/code/Magento/Quote/Test/Unit/Model/QuoteRepository/Plugin/AccessChangeQuoteControlTest.php +++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteRepository/Plugin/AccessChangeQuoteControlTest.php @@ -8,6 +8,7 @@ namespace Magento\Quote\Test\Unit\Model\QuoteRepository\Plugin; use Magento\Authorization\Model\UserContextInterface; +use Magento\Framework\Exception\StateException; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Quote\Model\ChangeQuoteControl; use Magento\Quote\Model\Quote; @@ -52,7 +53,7 @@ protected function setUp(): void $this->quoteMock = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() - ->setMethods(['getCustomerId']) + ->addMethods(['getCustomerId']) ->getMock(); $this->quoteRepositoryMock = $this->getMockBuilder(QuoteRepository::class) @@ -63,17 +64,10 @@ protected function setUp(): void ->disableOriginalConstructor() ->getMock(); - $objectManagerHelper = new ObjectManager($this); - $this->accessChangeQuoteControl = $objectManagerHelper->getObject( - AccessChangeQuoteControl::class, - ['changeQuoteControl' => $this->changeQuoteControlMock] - ); + $this->accessChangeQuoteControl = new AccessChangeQuoteControl($this->changeQuoteControlMock); } - /** - * User with role Customer and customer_id matches context user_id. - */ - public function testBeforeSaveForCustomer() + public function testBeforeSaveForCustomerWithCustomerIdMatchinQuoteUserIdIsAllowed() { $this->quoteMock->method('getCustomerId') ->willReturn(1); @@ -84,17 +78,12 @@ public function testBeforeSaveForCustomer() $this->changeQuoteControlMock->method('isAllowed') ->willReturn(true); - $result = $this->accessChangeQuoteControl->beforeSave($this->quoteRepositoryMock, $this->quoteMock); - - $this->assertNull($result); + $this->accessChangeQuoteControl->beforeSave($this->quoteRepositoryMock, $this->quoteMock); } - /** - * The user_id and customer_id from the quote are different. - */ - public function testBeforeSaveException() + public function testBeforeSaveThrowsExceptionForCustomerWithCustomerIdNotMatchingQuoteUserId() { - $this->expectException('Magento\Framework\Exception\StateException'); + $this->expectException(StateException::class); $this->expectExceptionMessage('Invalid state change requested'); $this->quoteMock->method('getCustomerId') ->willReturn(2); @@ -108,10 +97,7 @@ public function testBeforeSaveException() $this->accessChangeQuoteControl->beforeSave($this->quoteRepositoryMock, $this->quoteMock); } - /** - * User with role Admin and customer_id not much with user_id. - */ - public function testBeforeSaveForAdmin() + public function testBeforeSaveForAdminUserRoleIsAllowed() { $this->quoteMock->method('getCustomerId') ->willReturn(2); @@ -122,15 +108,10 @@ public function testBeforeSaveForAdmin() $this->changeQuoteControlMock->method('isAllowed') ->willReturn(true); - $result = $this->accessChangeQuoteControl->beforeSave($this->quoteRepositoryMock, $this->quoteMock); - - $this->assertNull($result); + $this->accessChangeQuoteControl->beforeSave($this->quoteRepositoryMock, $this->quoteMock); } - /** - * User with role Guest and customer_id === null. - */ - public function testBeforeSaveForGuest() + public function testBeforeSaveForGuestIsAllowed() { $this->quoteMock->method('getCustomerId') ->willReturn(null); @@ -141,17 +122,12 @@ public function testBeforeSaveForGuest() $this->changeQuoteControlMock->method('isAllowed') ->willReturn(true); - $result = $this->accessChangeQuoteControl->beforeSave($this->quoteRepositoryMock, $this->quoteMock); - - $this->assertNull($result); + $this->accessChangeQuoteControl->beforeSave($this->quoteRepositoryMock, $this->quoteMock); } - /** - * User with role Guest and customer_id !== null. - */ - public function testBeforeSaveForGuestException() + public function testBeforeSaveThrowsExceptionForGuestDoesNotEquals() { - $this->expectException('Magento\Framework\Exception\StateException'); + $this->expectException(StateException::class); $this->expectExceptionMessage('Invalid state change requested'); $this->quoteMock->method('getCustomerId') ->willReturn(1); @@ -165,12 +141,9 @@ public function testBeforeSaveForGuestException() $this->accessChangeQuoteControl->beforeSave($this->quoteRepositoryMock, $this->quoteMock); } - /** - * User with unknown role. - */ - public function testBeforeSaveForUnknownUserTypeException() + public function testBeforeSaveThrowsExceptionForUnknownUserType() { - $this->expectException('Magento\Framework\Exception\StateException'); + $this->expectException(StateException::class); $this->expectExceptionMessage('Invalid state change requested'); $this->quoteMock->method('getCustomerId') ->willReturn(2); diff --git a/app/code/Magento/Reports/Model/ResourceModel/Quote/Item/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Quote/Item/Collection.php index d219aefe81d45..16df2d30db40d 100644 --- a/app/code/Magento/Reports/Model/ResourceModel/Quote/Item/Collection.php +++ b/app/code/Magento/Reports/Model/ResourceModel/Quote/Item/Collection.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Reports\Model\ResourceModel\Quote\Item; @@ -17,6 +18,8 @@ */ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection { + private const PREPARED_FLAG_NAME = 'reports_collection_prepared'; + /** * Join fields * @@ -99,6 +102,11 @@ protected function _construct() public function prepareActiveCartItems() { $quoteItemsSelect = $this->getSelect(); + + if ($this->getFlag(self::PREPARED_FLAG_NAME)) { + return $quoteItemsSelect; + } + $quoteItemsSelect->reset() ->from(['main_table' => $this->getTable('quote_item')], '') ->columns('main_table.product_id') @@ -114,6 +122,7 @@ public function prepareActiveCartItems() )->group( 'main_table.product_id' ); + $this->setFlag(self::PREPARED_FLAG_NAME, true); return $quoteItemsSelect; } diff --git a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedGroupedBySkuTest.xml b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedGroupedBySkuTest.xml index a96e74c8ad166..beb1471bd6c4d 100644 --- a/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedGroupedBySkuTest.xml +++ b/app/code/Magento/Reports/Test/Mftf/Test/AdminReportsOrderedGroupedBySkuTest.xml @@ -45,7 +45,7 @@ <argument name="attribute" value="colorProductAttribute"/> <argument name="option" value="colorProductAttribute1"/> </actionGroup> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitFirstOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitFirstOrder" /> <!--Add second configurable product to order--> <actionGroup ref="NavigateToNewOrderPageExistingCustomerActionGroup" stepKey="navigateToSecondOrderWithExistingCustomer"> @@ -56,7 +56,7 @@ <argument name="attribute" value="colorProductAttribute"/> <argument name="option" value="colorProductAttribute2"/> </actionGroup> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitSecondOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitSecondOrder" /> <!-- Get date --> <generateDate stepKey="generateStartDate" date="-1 minute" format="m/d/Y"/> diff --git a/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Report/Quote/CollectionTest.php b/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Report/Quote/CollectionTest.php index ea8fcfbb77132..6e7d5bdce16f5 100644 --- a/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Report/Quote/CollectionTest.php +++ b/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Report/Quote/CollectionTest.php @@ -15,6 +15,7 @@ use Magento\Framework\Event\ManagerInterface; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Quote\Model\ResourceModel\Quote; +use Magento\Reports\Model\ResourceModel\Quote\Collection; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -42,7 +43,7 @@ protected function setUp(): void public function testGetSelectCountSql() { /** @var MockObject $collection */ - $collection = $this->getMockBuilder(\Magento\Reports\Model\ResourceModel\Quote\Collection::class) + $collection = $this->getMockBuilder(Collection::class) ->setMethods(['getSelect']) ->disableOriginalConstructor() ->getMock(); @@ -62,12 +63,12 @@ public function testPrepareActiveCartItems() $constructArgs = $this->objectManager ->getConstructArguments(\Magento\Reports\Model\ResourceModel\Quote\Item\Collection::class); $collection = $this->getMockBuilder(\Magento\Reports\Model\ResourceModel\Quote\Item\Collection::class) - ->setMethods(['getSelect', 'getTable']) + ->setMethods(['getSelect', 'getTable', 'getFlag', 'setFlag']) ->disableOriginalConstructor() ->setConstructorArgs($constructArgs) ->getMock(); - $collection->expects($this->once())->method('getSelect')->willReturn($this->selectMock); + $collection->expects($this->exactly(2))->method('getSelect')->willReturn($this->selectMock); $this->selectMock->expects($this->once())->method('reset')->willReturnSelf(); $this->selectMock->expects($this->once())->method('from')->willReturnSelf(); $this->selectMock->expects($this->atLeastOnce())->method('columns')->willReturnSelf(); @@ -75,7 +76,12 @@ public function testPrepareActiveCartItems() $this->selectMock->expects($this->once())->method('where')->willReturnSelf(); $this->selectMock->expects($this->once())->method('group')->willReturnSelf(); $collection->expects($this->exactly(2))->method('getTable')->willReturn('table'); + $collection->expects($this->once())->method('setFlag') + ->with('reports_collection_prepared')->willReturnSelf(); $collection->prepareActiveCartItems(); + $collection->method('getFlag') + ->with('reports_collection_prepared')->willReturn(true); + $this->assertEquals($this->selectMock, $collection->prepareActiveCartItems()); } public function testLoadWithFilter() diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php index d5a94a4dd1fcf..8ef12e5889520 100644 --- a/app/code/Magento/Sales/Model/AdminOrder/Create.php +++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php @@ -745,10 +745,12 @@ public function getCustomerCart() try { $this->_cart = $this->quoteRepository->getForCustomer($customerId, [$storeId]); } catch (\Magento\Framework\Exception\NoSuchEntityException $e) { - $this->_cart->setStore($this->getSession()->getStore()); - $customerData = $this->customerRepository->getById($customerId); - $this->_cart->assignCustomer($customerData); - $this->quoteRepository->save($this->_cart); + if ($this->getQuote()->hasItems()) { + $this->_cart->setStore($this->getSession()->getStore()); + $customerData = $this->customerRepository->getById($customerId); + $this->_cart->assignCustomer($customerData); + $this->quoteRepository->save($this->_cart); + } } } @@ -785,6 +787,7 @@ public function getCustomerCompareList() public function getCustomerGroupId() { $groupId = $this->getQuote()->getCustomerGroupId(); + // @phpstan-ignore-next-line if (!isset($groupId)) { $groupId = $this->getSession()->getCustomerGroupId(); } @@ -1443,9 +1446,10 @@ public function setShippingAddress($address) */ $saveInAddressBook = (int)(!empty($address['save_in_address_book'])); $shippingAddress->setData('save_in_address_book', $saveInAddressBook); - } - if ($address instanceof \Magento\Quote\Model\Quote\Address) { + } elseif ($address instanceof \Magento\Quote\Model\Quote\Address) { $shippingAddress = $address; + } else { + $shippingAddress = null; } $this->setRecollect(true); diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php index de15a627583ff..47395b17afee8 100644 --- a/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php +++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Handler/State.php @@ -9,7 +9,7 @@ use Magento\Sales\Model\Order; /** - * Class State + * Checking order status and adjusting order status before saving */ class State { @@ -34,6 +34,7 @@ public function check(Order $order) if (in_array($currentState, [Order::STATE_PROCESSING, Order::STATE_COMPLETE]) && !$order->canCreditmemo() && !$order->canShip() + && $order->getIsNotVirtual() ) { $order->setState(Order::STATE_CLOSED) ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_CLOSED)); diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderClickSubmitOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderClickSubmitOrderActionGroup.xml new file mode 100644 index 0000000000000..cf64806cda084 --- /dev/null +++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderClickSubmitOrderActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminOrderClickSubmitOrderActionGroup"> + <annotations> + <description>Click "Submit Order" button for order.</description> + </annotations> + + <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + <waitForPageLoad stepKey="waitForOrderPageToLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml index 6f4073bf70f46..127fd1dd4e006 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AddConfigurableProductToOrderFromShoppingCartTest.xml @@ -97,8 +97,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterCreatedCustomer"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickEditButton"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickEditButton"/> <!-- Click create order --> <click selector="{{AdminCustomerMainActionsSection.createOrderBtn}}" stepKey="clickCreateOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AddSimpleProductToOrderFromShoppingCartTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AddSimpleProductToOrderFromShoppingCartTest.xml index d8a9effa56dac..701b7ebe4a958 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AddSimpleProductToOrderFromShoppingCartTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AddSimpleProductToOrderFromShoppingCartTest.xml @@ -58,8 +58,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterCreatedCustomer"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickEditButton"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickEditButton"/> <!-- Click create order --> <click selector="{{AdminCustomerMainActionsSection.createOrderBtn}}" stepKey="clickCreateOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminAvailabilityCreditMemoWithNoPaymentTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminAvailabilityCreditMemoWithNoPaymentTest.xml index 0eb8d71223276..182549a6fe301 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminAvailabilityCreditMemoWithNoPaymentTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminAvailabilityCreditMemoWithNoPaymentTest.xml @@ -72,9 +72,7 @@ <!-- Select Free shipping --> <actionGroup ref="OrderSelectFreeShippingActionGroup" stepKey="selectFreeShippingOption"/> - - <!--Click *Submit Order* button--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="clickSubmitOrder" /> <!--Click *Invoice* button--> <actionGroup ref="StartCreateInvoiceFromOrderPageActionGroup" stepKey="startCreateInvoice"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithBankTransferPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithBankTransferPaymentMethodTest.xml index bb1940357a7f4..80336ea29e9d5 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithBankTransferPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithBankTransferPaymentMethodTest.xml @@ -59,9 +59,7 @@ <!-- Select bank Transfer payment method --> <waitForElementVisible selector="{{AdminOrderFormPaymentSection.paymentBlock}}" stepKey="waitForPaymentOptions"/> <conditionalClick selector="{{AdminOrderFormPaymentSection.bankTransferOption}}" dependentSelector="{{AdminOrderFormPaymentSection.bankTransferOption}}" visible="true" stepKey="checkBankTransferOption"/> - - <!-- Submit order --> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!-- Verify order information --> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCashOnDeliveryPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCashOnDeliveryPaymentMethodTest.xml index dafd00ff60b29..5f454152de20c 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCashOnDeliveryPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCashOnDeliveryPaymentMethodTest.xml @@ -59,9 +59,7 @@ <!-- Select Cash On Delivery payment method --> <waitForElementVisible selector="{{AdminOrderFormPaymentSection.paymentBlock}}" stepKey="waitForPaymentOptions"/> <checkOption selector="{{AdminOrderFormPaymentSection.cashOnDeliveryOption}}" stepKey="selectCashOnDeliveryPaymentOption"/> - - <!-- Submit order --> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml index 4e750c2cc24b3..7d6e2048c9432 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithCheckMoneyOrderPaymentMethodTest.xml @@ -149,7 +149,7 @@ <actionGroup ref="AdminSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod"/> <!-- Submit order --> <comment userInput="Submit order" stepKey="submitOrderComment"/> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!-- Verify order information --> <comment userInput="Verify order information" stepKey="verifyOrderInformationComment"/> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml index 62425cefb20db..f47513bcadedd 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithProductQtyWithoutStockDecreaseTest.xml @@ -58,9 +58,7 @@ <!--Select FlatRate shipping method--> <actionGroup ref="AdminSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod"/> - - <!--Submit order--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> @@ -109,7 +107,7 @@ <!-- Reorder the product --> <click selector="{{AdminOrderDetailsMainActionsSection.reorder}}" stepKey="clickOnReorderButton"/> <waitForPageLoad stepKey="waitForReorderFormToLoad"/> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder1"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder1" /> <!-- Assert Simple Product Quantity in backend after Reorder --> <actionGroup ref="FilterAndSelectProductActionGroup" stepKey="filterAndSelectTheProduct2"> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithPurchaseOrderPaymentMethodTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithPurchaseOrderPaymentMethodTest.xml index 0b873de34e279..9f09a59fa8d5e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithPurchaseOrderPaymentMethodTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithPurchaseOrderPaymentMethodTest.xml @@ -69,9 +69,7 @@ <fillField selector="{{AdminOrderFormPaymentSection.purchaseOrderNumber}}" userInput="{{PurchaseOrderNumber.number}}" stepKey="fillPurchaseOrderNumber"/> <click selector="{{AdminOrderFormActionSection.pageHeader}}" stepKey="clickOnHeader"/> <waitForPageLoad stepKey="waitForPageToLoad"/> - - <!--Submit order--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml index d22e11bca3d0e..6ce9909d06be5 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCancelTheCreatedOrderWithZeroSubtotalCheckoutTest.xml @@ -60,9 +60,7 @@ <!--Select FlatRate shipping method--> <actionGroup ref="AdminSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod"/> - - <!--Submit order--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml index 7818a1f3d9345..56399401b205e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateCreditMemoWithPurchaseOrderTest.xml @@ -61,8 +61,7 @@ <fillField selector="{{AdminOrderFormPaymentSection.fieldPurchaseOrderNumber}}" userInput="123456" stepKey="fillPONumber"/> <click selector="{{AdminOrderFormPaymentSection.blockPayment}}" stepKey="unfocus"/> <waitForPageLoad stepKey="waitForJavascriptToFinish"/> - <click selector="{{AdminOrderFormActionSection.submitOrder}}" stepKey="submitOrder"/> - <waitForPageLoad stepKey="waitForSubmitOrderPage"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <see stepKey="seeSuccessMessageForOrder" selector="{{AdminIndexManagementSection.successMessage}}" userInput="You created the order."/> <!-- Create Invoice --> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml index 627f739852ee7..30f8386b3bb91 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml @@ -34,7 +34,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductAdded"/> <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickCart"/> <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithBundleProductTest.xml index 1401930131b13..5c49d29ddf22e 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithBundleProductTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithBundleProductTest.xml @@ -93,9 +93,7 @@ <!--Select FlatRate shipping method--> <actionGroup ref="OrderSelectFlatRateShippingActionGroup" stepKey="orderSelectFlatRateShippingMethod"/> - - <!--Submit order--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml index 1f6d7c40be99b..b5c9e9443d1f9 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderWithMinimumAmountEnabledTest.xml @@ -63,9 +63,7 @@ <see selector="{{AdminOrderFormTotalSection.total('Subtotal')}}" userInput="${{AdminOrderSimpleProduct.subtotal}}" stepKey="seeOrderSubTotal"/> <see selector="{{AdminOrderFormTotalSection.total('Shipping')}}" userInput="${{AdminOrderSimpleProduct.shipping}}" stepKey="seeOrderShipping"/> <see selector="{{AdminOrderFormTotalSection.grandTotal}}" userInput="${{AdminOrderSimpleProduct.grandTotal}}" stepKey="seeCorrectGrandTotal"/> - - <!--Submit Order and verify information--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="clickSubmitOrder" /> <seeInCurrentUrl url="{{AdminOrderDetailsPage.url}}" stepKey="seeViewOrderPage"/> <see selector="{{AdminOrderDetailsMessagesSection.successMessage}}" userInput="You created the order." stepKey="seeSuccessMessage"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml index f6196c3a911ef..bf8e7e1868184 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminFreeShippingNotAvailableIfMinimumOrderAmountNotMatchOrderTotalTest.xml @@ -58,8 +58,7 @@ <!--Click *Get shipping methods and rates* and see that Free Shipping is absent--> <click selector="{{AdminOrderFormPaymentSection.getShippingMethods}}" stepKey="clickGetShippingMehods"/> <dontSeeElement selector="{{AdminOrderFormPaymentSection.freeShippingOption}}" stepKey="seeAbsentFreeShipping"/> - <!--Submit Order and verify that Order isn't placed--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="clickSubmitOrder" /> <dontSeeElement selector="{{AdminOrderFormMessagesSection.success}}" stepKey="seeSuccessMessage"/> <seeElement selector="{{AdminOrderFormMessagesSection.error}}" stepKey="seeErrorMessage"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminHoldCreatedOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminHoldCreatedOrderTest.xml index 8d328beab1adc..a73e64cfbca10 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminHoldCreatedOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminHoldCreatedOrderTest.xml @@ -60,9 +60,7 @@ <!--Select FlatRate shipping method--> <actionGroup ref="AdminSelectFlatRateShippingMethodActionGroup" stepKey="selectFlatRateShippingMethod"/> - - <!-- Submit order --> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!-- Verify order information --> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml index 54ae549967a3b..beaf098eee246 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitConfigurableProductOrderTest.xml @@ -114,9 +114,7 @@ <!-- Checkout select Check/Money Order payment --> <actionGroup ref="SelectCheckMoneyPaymentMethodActionGroup" stepKey="selectCheckMoneyPayment"/> - - <!--Submit order--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!--Verify order information--> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CheckXSSVulnerabilityDuringOrderCreationTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CheckXSSVulnerabilityDuringOrderCreationTest.xml index df6a797372e62..528b6b61f4842 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CheckXSSVulnerabilityDuringOrderCreationTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CheckXSSVulnerabilityDuringOrderCreationTest.xml @@ -54,7 +54,7 @@ <!-- Try to create order in admin with provided email --> <actionGroup ref="NavigateToNewOrderPageNewCustomerSingleStoreActionGroup" stepKey="navigateToNewOrderPage"/> <fillField selector="{{AdminOrderFormAccountSection.email}}" userInput="{{Simple_US_Customer_Incorrect_Email.email}}" stepKey="fillEmailAddressAdminPanel"/> - <click selector="{{AdminOrderFormActionSection.submitOrder}}" stepKey="clickSubmitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="clickSubmitOrder" /> <!-- Order can not be created --> <actionGroup ref="AssertAdminEmailValidationMessageOnCheckoutActionGroup" stepKey="assertErrorMessageAdminPanel"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml index 842faeb32cc33..ca705405809bd 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreateOrderFromEditCustomerPageTest.xml @@ -186,10 +186,7 @@ <waitForPageLoad stepKey="waitForShippingMethods"/> <click selector="{{AdminOrderFormPaymentSection.freeShippingOption}}" stepKey="chooseShippingMethod"/> <waitForPageLoad stepKey="waitForPageToLoad"/> - - <!-- Submit order --> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="submitOrder"/> - <waitForPageLoad stepKey="waitForAdminOrderFormLoad"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="submitOrder" /> <!-- Verify order information --> <actionGroup ref="VerifyCreatedOrderInformationActionGroup" stepKey="verifyCreatedOrderInformation"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml index 7444de8271ed9..e41e3acbae380 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/CreditMemoTotalAfterShippingDiscountTest.xml @@ -64,7 +64,7 @@ <amOnPage url="{{StorefrontCategoryPage.url($$createCategory.name$$)}}" stepKey="onCategoryPage"/> <waitForPageLoad stepKey="waitForPageLoad1"/> <moveMouseOver selector="{{StorefrontCategoryMainSection.ProductItemInfo}}" stepKey="hoverOverProduct"/> - <click selector="{{StorefrontCategoryMainSection.AddToCartBtn}}" stepKey="addToCart"/> + <actionGroup ref="StorefrontClickAddToCartButtonActionGroup" stepKey="addToCart"/> <waitForElementVisible selector="{{StorefrontCategoryMainSection.SuccessMsg}}" time="30" stepKey="waitForProductToAdd"/> <actionGroup ref="StorefrontClickOnMiniCartActionGroup" stepKey="clickCart"/> <click selector="{{StorefrontMinicartSection.goToCheckout}}" stepKey="goToCheckout"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml index c635e6b0ad6b2..8e9e117d2d995 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedConfigurableProductOnOrderPageTest.xml @@ -96,8 +96,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterCreatedCustomer"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickEditButton"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickEditButton"/> <!-- Click create order --> <click selector="{{AdminCustomerMainActionsSection.createOrderBtn}}" stepKey="clickCreateOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml index eb28ebfd068da..71da699e533bc 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveLastOrderedSimpleProductOnOrderPageTest.xml @@ -46,8 +46,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterCreatedCustomer"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickEditButton"/> - <waitForPageLoad stepKey="waitForPageLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickEditButton"/> <!-- Click create order --> <click selector="{{AdminCustomerMainActionsSection.createOrderBtn}}" stepKey="clickCreateOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedBundleFixedProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedBundleFixedProductOnOrderPageTest.xml index c3fc7a4952143..452d65ea5ae57 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedBundleFixedProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedBundleFixedProductOnOrderPageTest.xml @@ -96,8 +96,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterCreatedCustomer"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickEditButton"/> - <waitForPageLoad stepKey="waitForCustomerPageLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickEditButton"/> <!-- Click create order --> <click selector="{{AdminCustomerMainActionsSection.createOrderBtn}}" stepKey="clickCreateOrder"/> diff --git a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml index 0e021600ab3e3..4d1ebddc7c2b3 100644 --- a/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml +++ b/app/code/Magento/Sales/Test/Mftf/Test/MoveRecentlyViewedConfigurableProductOnOrderPageTest.xml @@ -99,8 +99,7 @@ <actionGroup ref="AdminFilterCustomerByEmail" stepKey="filterCreatedCustomer"> <argument name="email" value="$$createCustomer.email$$"/> </actionGroup> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickEditButton"/> - <waitForPageLoad stepKey="waitForCustomerPageLoad"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickEditButton"/> <!-- Click create order --> <click selector="{{AdminCustomerMainActionsSection.createOrderBtn}}" stepKey="clickCreateOrder"/> diff --git a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php index ea655bb32f05f..a48f0702f5a4b 100644 --- a/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php +++ b/app/code/Magento/Sales/Test/Unit/Model/ResourceModel/Order/Handler/StateTest.php @@ -41,7 +41,8 @@ protected function setUp(): void 'getBaseGrandTotal', 'canCreditmemo', 'getTotalRefunded', - 'getConfig' + 'getConfig', + 'getIsNotVirtual' ] ) ->disableOriginalConstructor() @@ -49,24 +50,21 @@ protected function setUp(): void $this->orderMock->expects($this->any()) ->method('getConfig') ->willReturnSelf(); - $this->addressMock = $this->createMock(Address::class); - $this->addressCollectionMock = $this->createMock( - Collection::class - ); $this->state = new State(); } /** - * @param bool $isCanceled - * @param bool $canUnhold - * @param bool $canInvoice - * @param bool $canShip - * @param int $callCanSkipNum * @param bool $canCreditmemo * @param int $callCanCreditmemoNum + * @param bool $canShip + * @param int $callCanSkipNum * @param string $currentState * @param string $expectedState - * @param int $callSetStateNum + * @param bool $isInProcess + * @param int $callGetIsInProcessNum + * @param bool $isCanceled + * @param bool $canUnhold + * @param bool $isNotVirtual * @dataProvider stateCheckDataProvider * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ @@ -76,12 +74,12 @@ public function testCheck( bool $canShip, int $callCanSkipNum, string $currentState, - string $expectedState = '', - bool $isInProcess = false, - int $callGetIsInProcessNum = 0, - bool $isCanceled = false, - bool $canUnhold = false, - bool $canInvoice = false + string $expectedState, + bool $isInProcess, + int $callGetIsInProcessNum, + bool $isCanceled, + bool $canUnhold, + bool $isNotVirtual ) { $this->orderMock->setState($currentState); $this->orderMock->expects($this->any()) @@ -92,7 +90,7 @@ public function testCheck( ->willReturn($canUnhold); $this->orderMock->expects($this->any()) ->method('canInvoice') - ->willReturn($canInvoice); + ->willReturn(false); $this->orderMock->expects($this->exactly($callCanSkipNum)) ->method('canShip') ->willReturn($canShip); @@ -102,11 +100,16 @@ public function testCheck( $this->orderMock->expects($this->exactly($callGetIsInProcessNum)) ->method('getIsInProcess') ->willReturn($isInProcess); + $this->orderMock->method('getIsNotVirtual') + ->willReturn($isNotVirtual); $this->state->check($this->orderMock); $this->assertEquals($expectedState, $this->orderMock->getState()); } /** + * Data provider for testCheck + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * @return array */ public function stateCheckDataProvider() @@ -118,7 +121,12 @@ public function stateCheckDataProvider() 'can_ship' => false, 'call_can_skip_num' => 1, 'current_state' => Order::STATE_PROCESSING, - 'expected_state' => Order::STATE_CLOSED + 'expected_state' => Order::STATE_CLOSED, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'complete - !canCreditmemo,!canShip -> closed' => [ 'can_credit_memo' => false, @@ -126,7 +134,12 @@ public function stateCheckDataProvider() 'can_ship' => false, 'call_can_skip_num' => 1, 'current_state' => Order::STATE_COMPLETE, - 'expected_state' => Order::STATE_CLOSED + 'expected_state' => Order::STATE_CLOSED, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'processing - !canCreditmemo,canShip -> processing' => [ 'can_credit_memo' => false, @@ -134,7 +147,12 @@ public function stateCheckDataProvider() 'can_ship' => true, 'call_can_skip_num' => 2, 'current_state' => Order::STATE_PROCESSING, - 'expected_state' => Order::STATE_PROCESSING + 'expected_state' => Order::STATE_PROCESSING, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'complete - !canCreditmemo,canShip -> complete' => [ 'can_credit_memo' => false, @@ -142,7 +160,12 @@ public function stateCheckDataProvider() 'can_ship' => true, 'call_can_skip_num' => 1, 'current_state' => Order::STATE_COMPLETE, - 'expected_state' => Order::STATE_COMPLETE + 'expected_state' => Order::STATE_COMPLETE, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'processing - canCreditmemo,!canShip -> complete' => [ 'can_credit_memo' => true, @@ -150,7 +173,12 @@ public function stateCheckDataProvider() 'can_ship' => false, 'call_can_skip_num' => 1, 'current_state' => Order::STATE_PROCESSING, - 'expected_state' => Order::STATE_COMPLETE + 'expected_state' => Order::STATE_COMPLETE, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'complete - canCreditmemo,!canShip -> complete' => [ 'can_credit_memo' => true, @@ -158,7 +186,12 @@ public function stateCheckDataProvider() 'can_ship' => false, 'call_can_skip_num' => 0, 'current_state' => Order::STATE_COMPLETE, - 'expected_state' => Order::STATE_COMPLETE + 'expected_state' => Order::STATE_COMPLETE, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'processing - canCreditmemo, canShip -> processing' => [ 'can_credit_memo' => true, @@ -166,7 +199,12 @@ public function stateCheckDataProvider() 'can_ship' => true, 'call_can_skip_num' => 1, 'current_state' => Order::STATE_PROCESSING, - 'expected_state' => Order::STATE_PROCESSING + 'expected_state' => Order::STATE_PROCESSING, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'complete - canCreditmemo, canShip -> complete' => [ 'can_credit_memo' => true, @@ -174,7 +212,12 @@ public function stateCheckDataProvider() 'can_ship' => true, 'call_can_skip_num' => 0, 'current_state' => Order::STATE_COMPLETE, - 'expected_state' => Order::STATE_COMPLETE + 'expected_state' => Order::STATE_COMPLETE, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'new - canCreditmemo, canShip, IsInProcess -> processing' => [ 'can_credit_memo' => true, @@ -183,8 +226,11 @@ public function stateCheckDataProvider() 'call_can_skip_num' => 1, 'current_state' => Order::STATE_NEW, 'expected_state' => Order::STATE_PROCESSING, - true, - 1 + 'is_in_process' => true, + 'get_is_in_process_invoke_count' => 1, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'new - canCreditmemo, !canShip, IsInProcess -> processing' => [ 'can_credit_memo' => true, @@ -193,8 +239,11 @@ public function stateCheckDataProvider() 'call_can_skip_num' => 1, 'current_state' => Order::STATE_NEW, 'expected_state' => Order::STATE_COMPLETE, - true, - 1 + 'is_in_process' => true, + 'get_is_in_process_invoke_count' => 1, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'new - canCreditmemo, canShip, !IsInProcess -> new' => [ 'can_credit_memo' => true, @@ -203,8 +252,11 @@ public function stateCheckDataProvider() 'call_can_skip_num' => 0, 'current_state' => Order::STATE_NEW, 'expected_state' => Order::STATE_NEW, - false, - 1 + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 1, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => true ], 'hold - canUnhold -> hold' => [ 'can_credit_memo' => true, @@ -213,10 +265,11 @@ public function stateCheckDataProvider() 'call_can_skip_num' => 0, 'current_state' => Order::STATE_HOLDED, 'expected_state' => Order::STATE_HOLDED, - false, - 0, - false, - true + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => true, + 'is_not_virtual' => true ], 'payment_review - canUnhold -> payment_review' => [ 'can_credit_memo' => true, @@ -225,10 +278,11 @@ public function stateCheckDataProvider() 'call_can_skip_num' => 0, 'current_state' => Order::STATE_PAYMENT_REVIEW, 'expected_state' => Order::STATE_PAYMENT_REVIEW, - false, - 0, - false, - true + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => true, + 'is_not_virtual' => true ], 'pending_payment - canUnhold -> pending_payment' => [ 'can_credit_memo' => true, @@ -237,10 +291,11 @@ public function stateCheckDataProvider() 'call_can_skip_num' => 0, 'current_state' => Order::STATE_PENDING_PAYMENT, 'expected_state' => Order::STATE_PENDING_PAYMENT, - false, - 0, - false, - true + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => true, + 'is_not_virtual' => true ], 'cancelled - isCanceled -> cancelled' => [ 'can_credit_memo' => true, @@ -249,9 +304,24 @@ public function stateCheckDataProvider() 'call_can_skip_num' => 0, 'current_state' => Order::STATE_HOLDED, 'expected_state' => Order::STATE_HOLDED, - false, - 0, - true + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => true, + 'can_unhold' => false, + 'is_not_virtual' => true + ], + 'processing - !canCreditmemo!canShip -> complete(virtual product)' => [ + 'can_credit_memo' => false, + 'can_credit_memo_invoke_count' => 1, + 'can_ship' => false, + 'call_can_skip_num' => 2, + 'current_state' => Order::STATE_PROCESSING, + 'expected_state' => Order::STATE_COMPLETE, + 'is_in_process' => false, + 'get_is_in_process_invoke_count' => 0, + 'is_canceled' => false, + 'can_unhold' => false, + 'is_not_virtual' => false ], ]; } diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml index 90b591a7bb1b1..c8ad0efdd4b4d 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Section/DiscountSection.xml @@ -14,6 +14,7 @@ <element name="ApplyCodeBtn" type="button" selector="//span[text()='Apply Discount']"/> <element name="CancelCoupon" type="button" selector="//button[@value='Cancel Coupon']"/> <element name="DiscountVerificationMsg" type="text" selector=".message-success div"/> + <element name="DiscountVerificationMsgWithAriaAtomicProperty" type="text" selector=".message-success[aria-atomic=true] div"/> <element name="CancelCouponBtn" type="button" selector="#discount-form .action-cancel"/> </section> </sections> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml index f32442ca5bc98..f956d036d7080 100644 --- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleEmptyFromDateTest.xml @@ -39,8 +39,7 @@ <!--Set timezone--> <!--Set timezone so we need compare with the same timezone used in "generateDate" action--> - <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfig"/> - <waitForPageLoad stepKey="waitForConfigPage"/> + <actionGroup ref="AdminOpenGeneralConfigurationPageActionGroup" stepKey="goToGeneralConfig"/> <wait stepKey="wait" time="10"/> <conditionalClick selector="{{LocaleOptionsSection.sectionHeader}}" dependentSelector="{{LocaleOptionsSection.timezone}}" visible="false" stepKey="openLocaleSection"/> <grabValueFrom selector="{{LocaleOptionsSection.timezone}}" stepKey="originalTimezone"/> @@ -93,8 +92,7 @@ <see selector="{{CheckoutCartSummarySection.discountAmount}}" userInput="-$5.00" stepKey="seeDiscountTotal"/> <!--Reset timezone--> - <amOnPage url="{{GeneralConfigurationPage.url}}" stepKey="goToGeneralConfigReset"/> - <waitForPageLoad stepKey="waitForConfigPageReset"/> + <actionGroup ref="AdminOpenGeneralConfigurationPageActionGroup" stepKey="goToGeneralConfigReset"/> <conditionalClick selector="{{LocaleOptionsSection.sectionHeader}}" dependentSelector="{{LocaleOptionsSection.timezone}}" visible="false" stepKey="openLocaleSectionReset"/> <selectOption selector="{{LocaleOptionsSection.timezone}}" userInput="$originalTimezone" stepKey="resetTimezone"/> <click selector="{{AdminMainActionsSection.save}}" stepKey="saveConfigReset"/> diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForBundleProductTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForBundleProductTest.xml new file mode 100644 index 0000000000000..101c72b78078a --- /dev/null +++ b/app/code/Magento/SalesRule/Test/Mftf/Test/CartPriceRuleForBundleProductTest.xml @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="CartPriceRuleForBundleProductTest"> + <annotations> + <features value="SalesRule"/> + <stories value="MAGETWO-28921 - Cart Price Rule for bundle products"/> + <title value="Checking Cart Price Rule for bundle products"/> + <description value="Checking Cart Price Rule for bundle products"/> + <severity value="BLOCKER"/> + <testCaseId value="MAGETWO-28921"/> + <group value="SalesRule"/> + </annotations> + + <before> + <!--Create 4 simple products--> + <createData entity="SimpleProduct2" stepKey="simpleProduct1"> + <field key="price">5.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct2"> + <field key="price">3.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct3"> + <field key="price">7.00</field> + </createData> + <createData entity="SimpleProduct2" stepKey="simpleProduct4"> + <field key="price">18.00</field> + </createData> + + <!-- Create the bundle product based --> + <createData entity="ApiBundleProduct" stepKey="createBundleProduct" /> + <createData entity="CheckboxOption" stepKey="createBundleOption1_1"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="CheckboxOption" stepKey="createBundleOption1_2"> + <requiredEntity createDataKey="createBundleProduct"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct1"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct2"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_1"/> + <requiredEntity createDataKey="simpleProduct2"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct3"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_2"/> + <requiredEntity createDataKey="simpleProduct3"/> + </createData> + <createData entity="ApiBundleLink" stepKey="linkOptionToProduct4"> + <requiredEntity createDataKey="createBundleProduct"/> + <requiredEntity createDataKey="createBundleOption1_2"/> + <requiredEntity createDataKey="simpleProduct4"/> + </createData> + + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + + <!-- Make Attribute 'sku' accessible for Promo Rule Conditions --> + <actionGroup ref="NavigateToEditProductAttributeActionGroup" stepKey="editSkuAttribute"> + <argument name="ProductAttribute" value="sku" /> + </actionGroup> + <actionGroup ref="ChangeUseForPromoRuleConditionsProductAttributeActionGroup" stepKey="changeAttributePromoRule"> + <argument name="option" value="1" /> + </actionGroup> + + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices" /> + </before> + + <after> + <!-- Delete created SalesRule --> + <actionGroup ref="DeleteCartPriceRuleByName" stepKey="DeleteCartPriceRuleByName"> + <argument name="ruleName" value="{{SimpleSalesRule.name}}"/> + </actionGroup> + + <!-- Delete Bundle product and it's children --> + <deleteData createDataKey="createBundleProduct" stepKey="createBundleProduct" /> + <deleteData createDataKey="simpleProduct1" stepKey="simpleProduct1" /> + <deleteData createDataKey="simpleProduct2" stepKey="simpleProduct2" /> + <deleteData createDataKey="simpleProduct3" stepKey="simpleProduct3" /> + <deleteData createDataKey="simpleProduct4" stepKey="simpleProduct4" /> + + <!-- Revert Attribute 'sku' to it's default value (not accessible for Promo Rule Conditions) --> + <actionGroup ref="NavigateToEditProductAttributeActionGroup" stepKey="editSkuAttribute"> + <argument name="ProductAttribute" value="sku" /> + </actionGroup> + <actionGroup ref="ChangeUseForPromoRuleConditionsProductAttributeActionGroup" stepKey="changeAttributePromoRule"> + <argument name="option" value="0" /> + </actionGroup> + + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + + <!-- Reindex invalidated indices after product attribute has been created/deleted --> + <magentoCron groups="index" stepKey="reindexInvalidatedIndices2" /> + </after> + + <!-- Create the rule --> + <amOnPage url="{{AdminCartPriceRulesPage.url}}" stepKey="amOnCartPriceList"/> + <waitForPageLoad stepKey="waitForRulesPage"/> + <click selector="{{AdminCartPriceRulesSection.addNewRuleButton}}" stepKey="clickAddNewRule"/> + <fillField selector="{{AdminCartPriceRulesFormSection.ruleName}}" userInput="{{SimpleSalesRule.name}}" stepKey="fillRuleName"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.websites}}" userInput="Main Website" stepKey="selectWebsites"/> + <actionGroup ref="SelectNotLoggedInCustomerGroupActionGroup" stepKey="selectNotLoggedInCustomerGroup"/> + <click selector="{{AdminCartPriceRulesFormSection.actionsHeader}}" stepKey="clickToExpandActions"/> + <fillField selector="{{AdminCartPriceRulesFormSection.discountAmount}}" userInput="10" stepKey="fillDiscountAmount"/> + <scrollTo selector="{{AdminCartPriceRulesFormSection.conditions}}" stepKey="ScrollToApplyRuleForConditions"/> + <click selector="{{AdminCartPriceRulesFormSection.conditions}}" stepKey="ApplyRuleForConditions"/> + <waitForPageLoad stepKey="waitForDropDownOpened"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.childAttribute}}" userInput="SKU" stepKey="selectAttribute"/> + <waitForPageLoad stepKey="waitForOperatorOpened"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('is')}}" stepKey="clickToChooseCondition"/> + <selectOption selector="{{AdminCartPriceRulesFormSection.operator}}" userInput="is one of" stepKey="selectOperator"/> + <waitForPageLoad stepKey="waitForOperatorOpened1"/> + <click selector="{{AdminCartPriceRulesFormSection.condition('...')}}" stepKey="clickToChooseOption"/> + <waitForPageLoad stepKey="waitForConditionOpened2"/> + <fillField selector="{{AdminCartPriceRulesFormSection.actionValue}}" userInput="$$simpleProduct1.sku$$" stepKey="fillSkuToFilters"/> + <waitForPageLoad stepKey="waitForPageLoaded"/> + <click selector="{{AdminCartPriceRulesFormSection.save}}" stepKey="clickSaveButton"/> + <see selector="{{AdminCartPriceRulesSection.messages}}" userInput="You saved the rule." stepKey="seeSuccessMessage"/> + + <!-- Add the first product to the cart --> + <amOnPage url="$$createBundleProduct.sku$$.html" stepKey="goToProductPage1"/> + <waitForPageLoad stepKey="waitForProductPageLoad1"/> + + <!--Click "Customize and Add to Cart" button--> + <click selector="{{StorefrontBundledSection.addToCart}}" stepKey="clickCustomize"/> + + <!-- Select two products --> + <click stepKey="selectProduct1" selector="{{StorefrontBundledSection.productCheckbox('1','1')}}"/> + <click stepKey="selectProduct2" selector="{{StorefrontBundledSection.productCheckbox('2','1')}}"/> + + <!--Click "Add to Cart" button--> + <click selector="{{StorefrontBundleProductActionSection.addToCartButton}}" stepKey="clickAddBundleProductToCart"/> + <waitForPageLoad time="30" stepKey="waitForAddBundleProductPageLoad"/> + + <!--Click "mini cart" icon--> + <actionGroup ref="StorefrontOpenCartFromMinicartActionGroup" stepKey="openCart"/> + <waitForPageLoad stepKey="waitForDetailsOpen"/> + + <!--Check all products and Cart Subtotal --> + <actionGroup ref="StorefrontCheckCartActionGroup" stepKey="cartAssert" after="waitForDetailsOpen"> + <argument name="subtotal" value="12.00"/> + <argument name="shipping" value="5.00"/> + <argument name="shippingMethod" value="Flat Rate - Fixed"/> + <argument name="total" value="16.50"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/AssertStorefrontVerifySearchButtonIsDisabledActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/AssertStorefrontVerifySearchButtonIsDisabledActionGroup.xml new file mode 100644 index 0000000000000..57d39e35d539e --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/AssertStorefrontVerifySearchButtonIsDisabledActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Filter by search query and select --> + <actionGroup name="AssertStorefrontVerifySearchButtonIsDisabledActionGroup"> + <annotations> + <description>Verify search button has disabled attribute</description> + </annotations> + + <grabAttributeFrom selector="{{StorefrontQuickSearchSection.searchButton}}" userInput="disabled" stepKey="grabSearchButtonDisabledAttribute"/> + + <assertEquals stepKey="assertSearchButtonDisabled"> + <actualResult type="const">$grabSearchButtonDisabledAttribute</actualResult> + <expectedResult type="string">true</expectedResult> + </assertEquals> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/AssertStorefrontVerifySearchButtonIsEnabledActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/AssertStorefrontVerifySearchButtonIsEnabledActionGroup.xml new file mode 100644 index 0000000000000..2e1f8d4b68d36 --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/AssertStorefrontVerifySearchButtonIsEnabledActionGroup.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <!-- Filter by search query and select --> + <actionGroup name="AssertStorefrontVerifySearchButtonIsEnabledActionGroup"> + <annotations> + <description>Verify search button does not disabled attribute</description> + </annotations> + + <grabAttributeFrom selector="{{StorefrontQuickSearchSection.searchButton}}" userInput="disabled" stepKey="grabSearchButtonAttribute"/> + + <assertEmpty stepKey="assertSearchButtonEnabled"> + <actualResult type="string">$grabSearchButtonAttribute</actualResult> + </assertEmpty> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontFillSearchActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontFillSearchActionGroup.xml new file mode 100644 index 0000000000000..f90297df02c1f --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontFillSearchActionGroup.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="StoreFrontFillSearchActionGroup"> + <arguments> + <argument name="query" type="string"/> + </arguments> + + <fillField stepKey="fillSearchField" selector="{{StorefrontQuickSearchSection.searchPhrase}}" userInput="{{query}}"/> + <waitForElementVisible selector="{{StorefrontQuickSearchSection.searchButton}}" stepKey="waitForSubmitButton"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml b/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml index f5bb414f59197..88e459178edbc 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml @@ -34,8 +34,7 @@ </after> <!-- Go to the catalog search term page --> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> - <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage"/> <!-- Select all created below search terms --> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByFirstSearchQuery"> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchButtonDisabledTillMinimumSearchLengthHitTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchButtonDisabledTillMinimumSearchLengthHitTest.xml new file mode 100644 index 0000000000000..742807d2c24e2 --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchButtonDisabledTillMinimumSearchLengthHitTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontVerifySearchButtonDisabledTillMinimumSearchLengthHitTest"> + <annotations> + <stories value="Search Term Disabled"/> + <title value="Verify search button is disabled if search term is less than minimum search length"/> + <description value="Storefront verify search button is disabled if search term is less than minimum search length"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-37380"/> + <group value="searchFrontend"/> + </annotations> + + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> + + <actionGroup ref="StoreFrontFillSearchActionGroup" stepKey="fillSearchByTextLessThanMinimumSearchLength"> + <argument name="query" value="Te"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontVerifySearchButtonIsDisabledActionGroup" stepKey="assertSearchButtonIsDisabled"/> + </test> +</tests> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchButtonEnabledAfterMinimumSearchLengthHitTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchButtonEnabledAfterMinimumSearchLengthHitTest.xml new file mode 100644 index 0000000000000..172fae919623c --- /dev/null +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchButtonEnabledAfterMinimumSearchLengthHitTest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontVerifySearchButtonEnabledAfterMinimumSearchLengthHitTest"> + <annotations> + <stories value="Search Button Not Disabled"/> + <title value="Verify search button is not disabled if search term is equal or greater than minimum search length"/> + <description value="Storefront verify search button is not disabled if search term is equal or greater than minimum search length"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-37381"/> + <group value="searchFrontend"/> + </annotations> + + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="openStoreFrontHomePage"/> + + <actionGroup ref="StoreFrontFillSearchActionGroup" stepKey="fillSearchByTextMoreThanMinimumSearchLength"> + <argument name="query" value="Magento"/> + </actionGroup> + + <actionGroup ref="AssertStorefrontVerifySearchButtonIsEnabledActionGroup" stepKey="assertSearchButtonIsNotDisabled"/> + </test> +</tests> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml index 22fcbfc2920ff..8c468cce91829 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml @@ -35,8 +35,7 @@ <!-- Delete created product --> <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> <!-- Go to the catalog search term page --> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> - <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage"/> <!-- Filter the search term --> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByThirdSearchQuery"> <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> @@ -60,8 +59,7 @@ <argument name="productName" value="$$simpleProduct.name$$"/> </actionGroup> <!-- Go to the catalog search term page --> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> - <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage"/> <!-- Filter the search term --> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByThirdSearchQuery"> <argument name="searchQuery" value="{{ApiProductDescription.value}}"/> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductNameTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductNameTest.xml index 0b02b49433dda..fb1f35730fd80 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductNameTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductNameTest.xml @@ -38,8 +38,7 @@ <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> <!-- Go to the catalog search term page --> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> - <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage"/> <!--Filter the search term --> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByThirdSearchQuery"> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductShortDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductShortDescriptionTest.xml index d88bb023c60b2..1558f9aa5342b 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductShortDescriptionTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductShortDescriptionTest.xml @@ -39,8 +39,7 @@ <deleteData createDataKey="product" stepKey="deleteProduct"/> <!--Go to the catalog search term page --> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> - <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage"/> <!--Filter the search term --> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByThirdSearchQuery"> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductSkuTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductSkuTest.xml index 4c586d18fd3cf..19c12843c23a2 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductSkuTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductSkuTest.xml @@ -39,8 +39,7 @@ <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> <!-- Go to the catalog search term page --> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="openAdminCatalogSearchTermIndexPage"/> - <waitForPageLoad stepKey="waitForAdminCatalogSearchTermIndexPageLoad"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="openAdminCatalogSearchTermIndexPage"/> <!--Filter the search term --> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="filterByThirdSearchQuery"> diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchTermEntityRedirectTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchTermEntityRedirectTest.xml index c5cbf1e0709c6..4f8cd9da856ca 100644 --- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchTermEntityRedirectTest.xml +++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchTermEntityRedirectTest.xml @@ -32,7 +32,7 @@ </actionGroup> </before> <after> - <amOnPage url="{{AdminCatalogSearchTermIndexPage.url}}" stepKey="navigateToSearchTermPage"/> + <actionGroup ref="AdminOpenCatalogSearchTermIndexPageActionGroup" stepKey="navigateToSearchTermPage"/> <actionGroup ref="AdminSearchTermFilterBySearchQueryActionGroup" stepKey="findCreatedTerm"> <argument name="searchQuery" value="{{SearchTerm.query_text}}"/> </actionGroup> diff --git a/app/code/Magento/Search/view/frontend/web/js/form-mini.js b/app/code/Magento/Search/view/frontend/web/js/form-mini.js index b4493c5f38089..9b4c814f73d73 100644 --- a/app/code/Magento/Search/view/frontend/web/js/form-mini.js +++ b/app/code/Magento/Search/view/frontend/web/js/form-mini.js @@ -232,8 +232,10 @@ define([ break; case $.ui.keyCode.ENTER: - this.searchForm.trigger('submit'); - e.preventDefault(); + if (this.element.val().length >= parseInt(this.options.minSearchLength, 10)) { + this.searchForm.trigger('submit'); + e.preventDefault(); + } break; case $.ui.keyCode.DOWN: @@ -294,9 +296,10 @@ define([ dropdown = $('<ul role="listbox"></ul>'), value = this.element.val(); - this.submitBtn.disabled = isEmpty(value); + this.submitBtn.disabled = true; if (value.length >= parseInt(this.options.minSearchLength, 10)) { + this.submitBtn.disabled = false; $.getJSON(this.options.url, { q: value }, $.proxy(function (data) { diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreatePartialShipmentEntityTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreatePartialShipmentEntityTest.xml index 9d501e4b34ef7..b1fb2aad54272 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreatePartialShipmentEntityTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreatePartialShipmentEntityTest.xml @@ -51,8 +51,7 @@ </actionGroup> <!-- Select Free shipping --> <actionGroup ref="OrderSelectFreeShippingActionGroup" stepKey="selectFreeShippingOption"/> - <!--Click *Submit Order* button--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="clickSubmitOrder" /> <!-- Create Partial Shipment --> <actionGroup ref="AdminCreateShipmentFromOrderPage" stepKey="createNewShipment"> <argument name="Qty" value="1"/> diff --git a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreateShipmentEntityTest.xml b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreateShipmentEntityTest.xml index a900a73fc36bc..5d46ef0a76263 100644 --- a/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreateShipmentEntityTest.xml +++ b/app/code/Magento/Shipping/Test/Mftf/Test/AdminCreateShipmentEntityTest.xml @@ -51,8 +51,7 @@ </actionGroup> <!-- Select Free shipping --> <actionGroup ref="OrderSelectFreeShippingActionGroup" stepKey="selectFreeShippingOption"/> - <!--Click *Submit Order* button--> - <click selector="{{AdminOrderFormActionSection.SubmitOrder}}" stepKey="clickSubmitOrder"/> + <actionGroup ref="AdminOrderClickSubmitOrderActionGroup" stepKey="clickSubmitOrder" /> <!-- Create Shipment --> <actionGroup ref="AdminCreateShipmentFromOrderPage" stepKey="createNewShipment"> <argument name="Title" value="Title"/> diff --git a/app/code/Magento/Store/Block/Switcher.php b/app/code/Magento/Store/Block/Switcher.php index f15349f11066d..a924805fcba90 100644 --- a/app/code/Magento/Store/Block/Switcher.php +++ b/app/code/Magento/Store/Block/Switcher.php @@ -170,9 +170,15 @@ public function getGroups() if ($store) { $group->setHomeUrl($store->getHomeUrl()); + $group->setSortOrder($store->getSortOrder()); $groups[] = $group; } } + + usort($groups, static function ($itemA, $itemB) { + return (int)$itemA->getSortOrder() <=> (int)$itemB->getSortOrder(); + }); + $this->setData('groups', $groups); } return $this->getData('groups'); @@ -193,7 +199,12 @@ public function getStores() $stores = []; } else { $stores = $rawStores[$groupId]; + + uasort($stores, static function ($itemA, $itemB) { + return (int)$itemA->getSortOrder() <=> (int)$itemB->getSortOrder(); + }); } + $this->setData('stores', $stores); } return $this->getData('stores'); diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCheckStoreViewOptionsActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCheckStoreViewOptionsActionGroup.xml new file mode 100644 index 0000000000000..ba96633a621c2 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCheckStoreViewOptionsActionGroup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCheckStoreViewOptionsActionGroup"> + <annotations> + <description>Goes to the Catalog->Product filters and check store view options at the Store View dropdown</description> + </annotations> + <arguments> + <argument name="storeViewId" type="string"/> + </arguments> + <amOnPage url="{{ProductCatalogPage.url}}" stepKey="OpenProductCatalogPage"/> + <waitForPageLoad stepKey="waitForProductCatalogPage"/> + <click selector="{{AdminProductGridFilterSection.filters}}" stepKey="clickFiltersButton"/> + <click selector="{{AdminProductFiltersSection.storeViewDropDown}}" stepKey="clickStoreViewSwitchDropdown"/> + <waitForElementVisible selector="{{AdminProductFiltersSection.storeViewDropDown}}" stepKey="waitForWebsiteAreVisible"/> + <seeElement selector="{{AdminProductGridFilterSection.storeViewOptions(storeViewId)}}" stepKey="seeStoreViewOption"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewFillSortOrderActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewFillSortOrderActionGroup.xml new file mode 100644 index 0000000000000..1b9b147209c66 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateStoreViewFillSortOrderActionGroup.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminCreateStoreViewFillSortOrderActionGroup" extends="AdminCreateStoreViewActionGroup"> + <annotations> + <description>Fill 'Sort Order' field</description> + </annotations> + <arguments> + <argument name="sortOrder" type="string" defaultValue="0"/> + </arguments> + + <fillField selector="{{AdminNewStoreSection.sortOrderTextField}}" userInput="{{sortOrder}}" stepKey="fillSortOrder" after="enterStoreViewCode"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml index bdb1842cf2959..39664ae10a07d 100644 --- a/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml +++ b/app/code/Magento/Store/Test/Mftf/Data/StoreData.xml @@ -206,4 +206,23 @@ <data key="store_type">store</data> <data key="store_action">add</data> </entity> + <!--Stores views with same name--> + <entity name="customStoreViewSameNameFirst" type="store"> + <data key="name">sameNameStoreView</data> + <data key="code" unique="suffix">storeViewCode</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_action">add</data> + <data key="store_type">store</data> + <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> + </entity> + <entity name="customStoreViewSameNameSecond" type="store"> + <data key="name">sameNameStoreView</data> + <data key="code" unique="suffix">storeViewCode</data> + <data key="is_active">1</data> + <data key="store_id">null</data> + <data key="store_action">add</data> + <data key="store_type">store</data> + <requiredEntity type="storeGroup">customStoreGroup</requiredEntity> + </entity> </entities> diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection/AdminStoresGridSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection/AdminStoresGridSection.xml index e56836c491276..cd7f180d0bb0e 100644 --- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection/AdminStoresGridSection.xml +++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection/AdminStoresGridSection.xml @@ -22,5 +22,6 @@ <element name="emptyText" type="text" selector="//tr[@class='data-grid-tr-no-data even']/td[@class='empty-text']"/> <element name="websiteName" type="text" selector="//td[@class='a-left col-website_title ']/a[contains(.,'{{websiteName}}')]" parameterized="true"/> <element name="gridCell" type="text" selector="//table[@class='data-grid']//tr[{{row}}]//td[count(//table[@class='data-grid']//tr//th[contains(., '{{column}}')]/preceding-sibling::th) +1 ]" parameterized="true"/> + <element name="storeViewLinkInNthRow" type="text" selector="tr:nth-of-type({{row}}) > .col-store_title > a" parameterized="true"/> </section> </sections> diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateDuplicateNameStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateDuplicateNameStoreViewTest.xml new file mode 100644 index 0000000000000..ec81424b1acfa --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateDuplicateNameStoreViewTest.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="AdminCreateDuplicateNameStoreViewTest"> + <annotations> + <features value="Store"/> + <stories value="Create a store view in admin"/> + <title value="Admin should be able to create a Store View with the same name"/> + <description value="Admin should be able to create a Store View with the same name"/> + <group value="storeView"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-36863"/> + </annotations> + <before> + <!--Create two store views with same name, but different codes--> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createFirstStoreView"> + <argument name="StoreGroup" value="_defaultStoreGroup"/> + <argument name="customStore" value="customStoreViewSameNameFirst"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewActionGroup" stepKey="createSecondStoreView"> + <argument name="StoreGroup" value="_defaultStoreGroup"/> + <argument name="customStore" value="customStoreViewSameNameSecond"/> + </actionGroup> + </before> + <after> + <!--Delete both store views--> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteFirstStoreView"> + <argument name="customStore" value="customStoreViewSameNameFirst"/> + </actionGroup> + <actionGroup ref="AdminDeleteStoreViewActionGroup" stepKey="deleteSecondStoreView"> + <argument name="customStore" value="customStoreViewSameNameSecond"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + </after> + <!--Get Id of store views--> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="navigateToStoreViews"/> + <click selector="{{AdminStoresGridSection.storeViewLinkInNthRow('2')}}" stepKey="openFirstViewPAge" /> + <grabFromCurrentUrl stepKey="getStoreViewIdFirst" regex="~/store_id/(\d+)/~"/> + <amOnPage url="{{AdminSystemStorePage.url}}" stepKey="navigateToStoreViewsAgain"/> + <click selector="{{AdminStoresGridSection.storeViewLinkInNthRow('3')}}" stepKey="openSecondViewPAge" /> + <grabFromCurrentUrl stepKey="getStoreViewIdSecond" regex="~/store_id/(\d+)/~"/> + <!--Go to catalog -> product grid, open the filter and check the listed store view--> + <actionGroup ref="AdminCheckStoreViewOptionsActionGroup" stepKey="checkFirstStoreView"> + <argument name="storeViewId" value="{$getStoreViewIdFirst}"/> + </actionGroup> + <actionGroup ref="AdminCheckStoreViewOptionsActionGroup" stepKey="checkSecondStoreView"> + <argument name="storeViewId" value="{$getStoreViewIdSecond}"/> + </actionGroup> + </test> +</tests> diff --git a/app/code/Magento/Store/Test/Mftf/Test/StorefrontCheckSortOrderStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/StorefrontCheckSortOrderStoreViewTest.xml new file mode 100644 index 0000000000000..442ee99e12793 --- /dev/null +++ b/app/code/Magento/Store/Test/Mftf/Test/StorefrontCheckSortOrderStoreViewTest.xml @@ -0,0 +1,72 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StorefrontCheckSortOrderStoreView"> + <annotations> + <features value="Backend"/> + <stories value="Github issue: #13401 'Store View' sort order values are not reflected"/> + <title value="Check 'Store view' sort order values"/> + <description value="Check 'Store View' sort order values no frontend store-switcher"/> + <severity value="MINOR"/> + <group value="store"/> + </annotations> + <before> + <actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createFirstStore"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{customStoreGroup.name}}"/> + <argument name="storeGroupCode" value="{{customStoreGroup.code}}"/> + </actionGroup> + <actionGroup ref="AdminCreateNewStoreGroupActionGroup" stepKey="createSecondStore"> + <argument name="website" value="{{_defaultWebsite.name}}"/> + <argument name="storeGroupName" value="{{SecondStoreGroupUnique.name}}"/> + <argument name="storeGroupCode" value="{{SecondStoreGroupUnique.code}}"/> + </actionGroup> + </before> + <after> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteCustomStore"> + <argument name="storeGroupName" value="customStoreGroup.name"/> + </actionGroup> + <actionGroup ref="DeleteCustomStoreActionGroup" stepKey="deleteSecondStore"> + <argument name="storeGroupName" value="SecondStoreGroupUnique.name"/> + </actionGroup> + <actionGroup ref="AdminLogoutActionGroup" stepKey="logout"/> + <actionGroup ref="CliIndexerReindexActionGroup" stepKey="reindex"> + <argument name="indices" value=""/> + </actionGroup> + <actionGroup ref="CliCacheFlushActionGroup" stepKey="flushCache"> + <argument name="tags" value=""/> + </actionGroup> + </after> + <actionGroup ref="AdminCreateStoreViewFillSortOrderActionGroup" stepKey="createFirstStoreView"> + <argument name="StoreGroup" value="customStoreGroup"/> + <argument name="customStore" value="customStoreGroup"/> + <argument name="sortOrder" value="30"/> + </actionGroup> + <actionGroup ref="AdminCreateStoreViewFillSortOrderActionGroup" stepKey="createSecondStoreView"> + <argument name="StoreGroup" value="SecondStoreGroupUnique"/> + <argument name="customStore" value="SecondStoreGroupUnique"/> + <argument name="sortOrder" value="20"/> + </actionGroup> + + <actionGroup ref="StorefrontOpenHomePageActionGroup" stepKey="goToHomePage"/> + <click stepKey="selectStoreSwitcher" selector="{{StorefrontFooterSection.switchStoreButton}}"/> + <grabTextFrom selector="{{StorefrontFooterSection.storeViewOptionNumber('1')}}" stepKey="grabSwatchFirstOption"/> + <grabTextFrom selector="{{StorefrontFooterSection.storeViewOptionNumber('2')}}" stepKey="grabSwatchSecondOption"/> + <assertStringContainsString stepKey="checkingSwatchFirstOption"> + <expectedResult type="string">{{SecondStoreGroupUnique.name}}</expectedResult> + <actualResult type="variable">$grabSwatchFirstOption</actualResult> + </assertStringContainsString> + <assertStringContainsString stepKey="checkingSwatchSecondOption"> + <expectedResult type="string">{{customStoreGroup.name}}</expectedResult> + <actualResult type="variable">$grabSwatchSecondOption</actualResult> + </assertStringContainsString> + </test> +</tests> diff --git a/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php b/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php index 9106da8ffb177..60c69834f6aa6 100644 --- a/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php +++ b/app/code/Magento/Store/Test/Unit/Block/SwitcherTest.php @@ -7,91 +7,159 @@ namespace Magento\Store\Test\Unit\Block; +use Magento\Directory\Helper\Data; +use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\Data\Helper\PostHelper; use Magento\Framework\TestFramework\Unit\Helper\ObjectManager; use Magento\Framework\UrlInterface; use Magento\Framework\View\Element\Template\Context; use Magento\Store\Api\Data\StoreInterface; use Magento\Store\Block\Switcher; +use Magento\Store\Model\ScopeInterface; use Magento\Store\Model\Store; use Magento\Store\Model\StoreManagerInterface; +use Magento\Store\Model\Website; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +/** + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + */ class SwitcherTest extends TestCase { - /** @var Switcher */ - protected $switcher; - - /** @var Context|MockObject */ - protected $context; + /** + * @var Switcher + */ + private $switcher; - /** @var PostHelper|MockObject */ - protected $corePostDataHelper; + /** + * @var PostHelper|MockObject + */ + private $corePostDataHelperMock; - /** @var StoreManagerInterface|MockObject */ - protected $storeManager; + /** + * @var StoreManagerInterface|MockObject + */ + private $storeManagerMock; - /** @var UrlInterface|MockObject */ - protected $urlBuilder; + /** + * @var UrlInterface|MockObject + */ + private $urlBuilderMock; - /** @var StoreInterface|MockObject */ - private $store; + /** + * @var ScopeConfigInterface|MockObject + */ + private $scopeConfigMock; /** * @return void */ protected function setUp(): void { - $this->storeManager = $this->getMockBuilder(StoreManagerInterface::class) - ->getMock(); - $this->urlBuilder = $this->getMockForAbstractClass(UrlInterface::class); - $this->context = $this->createMock(Context::class); - $this->context->expects($this->any())->method('getStoreManager')->willReturn($this->storeManager); - $this->context->expects($this->any())->method('getUrlBuilder')->willReturn($this->urlBuilder); - $this->corePostDataHelper = $this->createMock(PostHelper::class); - $this->store = $this->getMockBuilder(StoreInterface::class) - ->disableOriginalConstructor() - ->getMockForAbstractClass(); + $this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class)->getMock(); + $this->urlBuilderMock = $this->createMock(UrlInterface::class); + $this->scopeConfigMock = $this->createMock(ScopeConfigInterface::class); + $contextMock = $this->createMock(Context::class); + $contextMock->method('getStoreManager')->willReturn($this->storeManagerMock); + $contextMock->method('getUrlBuilder')->willReturn($this->urlBuilderMock); + $contextMock->method('getScopeConfig')->willReturn($this->scopeConfigMock); + $this->corePostDataHelperMock = $this->createMock(PostHelper::class); $this->switcher = (new ObjectManager($this))->getObject( Switcher::class, [ - 'context' => $this->context, - 'postDataHelper' => $this->corePostDataHelper, + 'context' => $contextMock, + 'postDataHelper' => $this->corePostDataHelperMock, ] ); } + public function testGetStoresSortOrder() + { + $groupId = 1; + $storesSortOrder = [ + 1 => 2, + 2 => 4, + 3 => 1, + 4 => 3 + ]; + + $currentStoreMock = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->getMock(); + $currentStoreMock->method('getGroupId')->willReturn($groupId); + $currentStoreMock->method('isUseStoreInUrl')->willReturn(false); + $this->storeManagerMock->method('getStore') + ->willReturn($currentStoreMock); + + $currentWebsiteMock = $this->getMockBuilder(Website::class) + ->disableOriginalConstructor() + ->getMock(); + $this->storeManagerMock->method('getWebsite') + ->willReturn($currentWebsiteMock); + + $stores = []; + foreach ($storesSortOrder as $storeId => $sortOrder) { + $storeMock = $this->getMockBuilder(Store::class) + ->disableOriginalConstructor() + ->setMethods(['getId', 'getGroupId', 'getSortOrder', 'isActive', 'getUrl']) + ->getMock(); + $storeMock->method('getId')->willReturn($storeId); + $storeMock->method('getGroupId')->willReturn($groupId); + $storeMock->method('getSortOrder')->willReturn($sortOrder); + $storeMock->method('isActive')->willReturn(true); + $storeMock->method('getUrl')->willReturn('https://example.org'); + $stores[] = $storeMock; + } + + $scopeConfigMap = array_map(static function ($item) { + return [ + Data::XML_PATH_DEFAULT_LOCALE, + ScopeInterface::SCOPE_STORE, + $item, + 'en_US' + ]; + }, $stores); + $this->scopeConfigMock->method('getValue') + ->willReturnMap($scopeConfigMap); + + $currentWebsiteMock->method('getStores') + ->willReturn($stores); + + $this->assertEquals([3, 1, 4, 2], array_keys($this->switcher->getStores())); + } + /** * @return void */ public function testGetTargetStorePostData() { - $store = $this->getMockBuilder(Store::class) + $storeMock = $this->getMockBuilder(Store::class) ->disableOriginalConstructor() ->getMock(); - $store->expects($this->any()) - ->method('getCode') + $oldStoreMock = $this->getMockBuilder(StoreInterface::class) + ->disableOriginalConstructor() + ->getMockForAbstractClass(); + $storeMock->method('getCode') ->willReturn('new-store'); $storeSwitchUrl = 'http://domain.com/stores/store/redirect'; - $store->expects($this->atLeastOnce()) + $storeMock->expects($this->atLeastOnce()) ->method('getCurrentUrl') ->with(false) ->willReturn($storeSwitchUrl); - $this->storeManager->expects($this->once()) + $this->storeManagerMock->expects($this->once()) ->method('getStore') - ->willReturn($this->store); - $this->store->expects($this->once()) + ->willReturn($oldStoreMock); + $oldStoreMock->expects($this->once()) ->method('getCode') ->willReturn('old-store'); - $this->urlBuilder->expects($this->once()) + $this->urlBuilderMock->expects($this->once()) ->method('getUrl') ->willReturn($storeSwitchUrl); - $this->corePostDataHelper->expects($this->any()) - ->method('getPostData') + $this->corePostDataHelperMock->method('getPostData') ->with($storeSwitchUrl, ['___store' => 'new-store', 'uenc' => null, '___from_store' => 'old-store']); - $this->switcher->getTargetStorePostData($store); + $this->switcher->getTargetStorePostData($storeMock); } /** @@ -104,7 +172,7 @@ public function testIsStoreInUrl($isUseStoreInUrl) $storeMock->expects($this->once())->method('isUseStoreInUrl')->willReturn($isUseStoreInUrl); - $this->storeManager->expects($this->any())->method('getStore')->willReturn($storeMock); + $this->storeManagerMock->method('getStore')->willReturn($storeMock); $this->assertEquals($this->switcher->isStoreInUrl(), $isUseStoreInUrl); // check value is cached $this->assertEquals($this->switcher->isStoreInUrl(), $isUseStoreInUrl); @@ -114,7 +182,7 @@ public function testIsStoreInUrl($isUseStoreInUrl) * @see self::testIsStoreInUrlDataProvider() * @return array */ - public function isStoreInUrlDataProvider() + public function isStoreInUrlDataProvider(): array { return [[true], [false]]; } diff --git a/app/code/Magento/Store/Ui/Component/Listing/Column/Store/Options.php b/app/code/Magento/Store/Ui/Component/Listing/Column/Store/Options.php index 907eb74e20fa2..f8aa09cb20a61 100644 --- a/app/code/Magento/Store/Ui/Component/Listing/Column/Store/Options.php +++ b/app/code/Magento/Store/Ui/Component/Listing/Column/Store/Options.php @@ -10,7 +10,7 @@ use Magento\Store\Model\System\Store as SystemStore; /** - * Class Options + * Ui stores options */ class Options implements OptionSourceInterface { @@ -93,37 +93,38 @@ protected function sanitizeName($name) * * @return void */ - protected function generateCurrentOptions() + protected function generateCurrentOptions(): void { $websiteCollection = $this->systemStore->getWebsiteCollection(); $groupCollection = $this->systemStore->getGroupCollection(); $storeCollection = $this->systemStore->getStoreCollection(); - /** @var \Magento\Store\Model\Website $website */ + foreach ($websiteCollection as $website) { $groups = []; - /** @var \Magento\Store\Model\Group $group */ foreach ($groupCollection as $group) { - if ($group->getWebsiteId() == $website->getId()) { + if ($group->getWebsiteId() === $website->getId()) { $stores = []; - /** @var \Magento\Store\Model\Store $store */ foreach ($storeCollection as $store) { - if ($store->getGroupId() == $group->getId()) { - $name = $this->sanitizeName($store->getName()); - $stores[$name]['label'] = str_repeat(' ', 8) . $name; - $stores[$name]['value'] = $store->getId(); + if ($store->getGroupId() === $group->getId()) { + $stores[] = [ + 'label' => str_repeat(' ', 8) . $this->sanitizeName($store->getName()), + 'value' => $store->getId(), + ]; } } if (!empty($stores)) { - $name = $this->sanitizeName($group->getName()); - $groups[$name]['label'] = str_repeat(' ', 4) . $name; - $groups[$name]['value'] = array_values($stores); + $groups[] = [ + 'label' => str_repeat(' ', 4) . $this->sanitizeName($group->getName()), + 'value' => array_values($stores), + ]; } } } if (!empty($groups)) { - $name = $this->sanitizeName($website->getName()); - $this->currentOptions[$name]['label'] = $name; - $this->currentOptions[$name]['value'] = array_values($groups); + $this->currentOptions[] = [ + 'label' => $this->sanitizeName($website->getName()), + 'value' => array_values($groups), + ]; } } } diff --git a/app/code/Magento/Swatches/Test/Mftf/Test/AdminSetUpWatermarkForSwatchImageTest.xml b/app/code/Magento/Swatches/Test/Mftf/Test/AdminSetUpWatermarkForSwatchImageTest.xml index d56572afd8847..07ce30b702f91 100644 --- a/app/code/Magento/Swatches/Test/Mftf/Test/AdminSetUpWatermarkForSwatchImageTest.xml +++ b/app/code/Magento/Swatches/Test/Mftf/Test/AdminSetUpWatermarkForSwatchImageTest.xml @@ -38,8 +38,7 @@ </actionGroup> <!-- Select Edit next to the Default Store View --> <comment userInput="Select Edit next to the Default Store View" stepKey="commentEditDefaultView"/> - <click selector="{{AdminCustomerGridSection.firstRowEditLink}}" stepKey="clickToEditDefaultStoreView"/> - <waitForPageLoad stepKey="waitForDefaultStorePage"/> + <actionGroup ref="AdminClickFirstRowEditLinkOnCustomerGridActionGroup" stepKey="clickToEditDefaultStoreView"/> <!-- Expand the Product Image Watermarks section--> <comment userInput="Expand the Product Image Watermarks section" stepKey="commentOpenWatermarksSection"/> <click selector="{{AdminDesignConfigSection.watermarkSectionHeader}}" stepKey="clickToProductImageWatermarks"/> diff --git a/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminClickAddTaxRuleButtonActionGroup.xml b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminClickAddTaxRuleButtonActionGroup.xml new file mode 100644 index 0000000000000..ea5c4cb74d19e --- /dev/null +++ b/app/code/Magento/Tax/Test/Mftf/ActionGroup/AdminClickAddTaxRuleButtonActionGroup.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd"> + <actionGroup name="AdminClickAddTaxRuleButtonActionGroup"> + <annotations> + <description>Click button for creating new tax rule.</description> + </annotations> + <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> + <waitForPageLoad stepKey="waitForTaxRuleGridLoad"/> + </actionGroup> +</actionGroups> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateDefaultsTaxRuleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateDefaultsTaxRuleTest.xml index 07968c281c68b..5e7ce53a3a3fc 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateDefaultsTaxRuleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateDefaultsTaxRuleTest.xml @@ -30,8 +30,7 @@ </after> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> - <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAddNewTaxRuleButton"/> <!-- Create a tax rule with defaults --> <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$initialTaxRate.code$$" stepKey="fillTaxRateSearch"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateLargeRateTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateLargeRateTest.xml index c8e4defc40c9f..89cfdd0eb9943 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateLargeRateTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateLargeRateTest.xml @@ -59,7 +59,7 @@ <!-- Verify we see expected values on the tax rule form page --> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAdd"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAdd"/> <see selector="{{AdminTaxRulesSection.taxRateMultiSelectItems}}" userInput="{{SimpleTaxRate.code}}" stepKey="seeTaxRateOnNewTaxRulePage"/> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateSpecificPostcodeTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateSpecificPostcodeTest.xml index c6a5a6c69e788..a3386cada436f 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateSpecificPostcodeTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateSpecificPostcodeTest.xml @@ -58,7 +58,7 @@ <!-- Verify we see expected values on the tax rule form page --> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAdd"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAdd"/> <see selector="{{AdminTaxRulesSection.taxRateMultiSelectItems}}" userInput="{{SimpleTaxRate.code}}" stepKey="seeTaxRateOnNewTaxRulePage"/> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateWiderZipCodeRangeTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateWiderZipCodeRangeTest.xml index ef9b66041893d..6ceeae953139c 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateWiderZipCodeRangeTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateWiderZipCodeRangeTest.xml @@ -60,7 +60,7 @@ <!-- Verify we see expected values on the tax rule form page --> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAdd"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAdd"/> <see selector="{{AdminTaxRulesSection.taxRateMultiSelectItems}}" userInput="{{SimpleTaxRate.code}}" stepKey="seeTaxRateOnNewTaxRulePage"/> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateZipCodeRangeTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateZipCodeRangeTest.xml index 23c4ffd78a88d..4f9e876fed696 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateZipCodeRangeTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRateZipCodeRangeTest.xml @@ -62,7 +62,7 @@ <!-- Verify we see expected values on the tax rule form page --> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAdd"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAdd"/> <see selector="{{AdminTaxRulesSection.taxRateMultiSelectItems}}" userInput="{{SimpleTaxRate.code}}" stepKey="seeTaxRateOnNewTaxRulePage"/> </test> </tests> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithCustomerAndProductTaxClassTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithCustomerAndProductTaxClassTest.xml index ba0834da7c0e7..f1a48af741cd6 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithCustomerAndProductTaxClassTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithCustomerAndProductTaxClassTest.xml @@ -40,8 +40,7 @@ </after> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> - <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAddNewTaxRuleButton"/> <!-- Create a tax rule with customer and product class --> <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewAndExistingTaxRateAndCustomerAndProductTaxClassTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewAndExistingTaxRateAndCustomerAndProductTaxClassTest.xml index ae37bc8a8930a..de7a0fb2d9144 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewAndExistingTaxRateAndCustomerAndProductTaxClassTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewAndExistingTaxRateAndCustomerAndProductTaxClassTest.xml @@ -41,8 +41,7 @@ </after> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> - <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAddNewTaxRuleButton"/> <!-- Create a tax rule with new and existing tax rate, customer tax class, product tax class --> <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewTaxClassesAndTaxRateTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewTaxClassesAndTaxRateTest.xml index 2a008991c2dc8..4798ec60ab898 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewTaxClassesAndTaxRateTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithNewTaxClassesAndTaxRateTest.xml @@ -41,8 +41,7 @@ </after> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> - <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAddNewTaxRuleButton"/> <!-- Create a tax rule with new tax classes and tax rate --> <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithZipRangeTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithZipRangeTest.xml index de55453fcabc4..a08c878ba2063 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithZipRangeTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminCreateTaxRuleWithZipRangeTest.xml @@ -41,8 +41,7 @@ </after> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex1"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> - <waitForPageLoad stepKey="waitForTaxRuleIndex2"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAddNewTaxRuleButton"/> <!-- Create a tax rule with new tax classes and tax rate --> <fillField selector="{{AdminTaxRuleFormSection.code}}" userInput="{{SimpleTaxRule.code}}" stepKey="fillTaxRuleCode1"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/DeleteTaxRateEntityTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/DeleteTaxRateEntityTest.xml index 881e09e5e35f4..eb774297b8322 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/DeleteTaxRateEntityTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/DeleteTaxRateEntityTest.xml @@ -44,8 +44,7 @@ <!-- Confirm Deleted TaxIdentifier on the tax rule grid page --> <actionGroup ref="AdminTaxRuleGridOpenPageActionGroup" stepKey="goToTaxRuleIndex3"/> - <click selector="{{AdminTaxRuleGridSection.add}}" stepKey="clickAddNewTaxRuleButton"/> - <waitForPageLoad stepKey="waitForTaxRuleIndex1"/> + <actionGroup ref="AdminClickAddTaxRuleButtonActionGroup" stepKey="clickAddNewTaxRuleButton"/> <fillField selector="{{AdminTaxRuleFormSection.taxRateSearch}}" userInput="$$initialTaxRate.code$$" stepKey="fillTaxRateSearch"/> <wait stepKey="waitForSearch" time="5" /> <dontSee selector="{{AdminTaxRuleFormSection.fieldTaxRate}}" userInput="$$initialTaxRate.code$$" stepKey="dontSeeInTaxRuleForm"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml index b9c3baab8c0dd..d2f1b1aa44393 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestSimpleTest.xml @@ -79,8 +79,7 @@ <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> <!-- Fill in address for CA --> - <amOnPage url="{{CheckoutPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForShippingSection"/> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="goToCheckout"/> <fillField selector="{{CheckoutShippingSection.email}}" userInput="{{Simple_US_Customer_CA.email}}" stepKey="enterEmail"/> <waitForLoadingMaskToDisappear stepKey="waitEmailLoad"/> <actionGroup ref="LoggedInCheckoutFillNewBillingAddressActionGroup" stepKey="changeAddress"> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml index 8fafbd9986c64..5441664d7c530 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutGuestVirtualTest.xml @@ -79,8 +79,7 @@ <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for CA --> - <amOnPage url="{{CheckoutPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForShippingSection"/> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="goToCheckout"/> <waitForElementVisible stepKey="waitForOverviewVisible" selector="{{CheckoutPaymentSection.tax}}"/> <see stepKey="seeTax2" selector="{{CheckoutPaymentSection.tax}}" userInput="$8.25"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml index ae988cd43efd5..76f4dcd8e161e 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInSimpleTest.xml @@ -94,8 +94,7 @@ <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> - <amOnPage url="{{CheckoutPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForShippingSection"/> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="goToCheckout"/> <see stepKey="seeAddress" selector="{{CheckoutShippingSection.selectedShippingAddress}}" userInput="{{SimpleTaxNY.state}}"/> <actionGroup ref="StorefrontCheckoutClickNextOnShippingStepActionGroup" stepKey="clickNext"/> diff --git a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml index 6552d31a8a523..c98765976f36f 100644 --- a/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml +++ b/app/code/Magento/Tax/Test/Mftf/Test/StorefrontTaxQuoteCheckoutTest/StorefrontTaxQuoteCheckoutLoggedInVirtualTest.xml @@ -94,8 +94,7 @@ <see stepKey="seeSuccess" selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You added"/> <!-- Assert that taxes are applied correctly for NY --> - <amOnPage url="{{CheckoutPage.url}}" stepKey="goToCheckout"/> - <waitForPageLoad stepKey="waitForShippingSection"/> + <actionGroup ref="StorefrontOpenCheckoutPageActionGroup" stepKey="goToCheckout"/> <!-- Checkout select Check/Money Order payment --> <actionGroup ref="CheckoutSelectCheckMoneyOrderPaymentActionGroup" stepKey="selectCheckMoneyPayment"/> <see stepKey="seeAddress" selector="{{CheckoutShippingSection.defaultShipping}}" userInput="{{SimpleTaxNY.state}}"/> diff --git a/app/code/Magento/Theme/Model/Indexer/Design/Config/Plugin/Store.php b/app/code/Magento/Theme/Model/Indexer/Design/Config/Plugin/Store.php index ca4a238fd1ff4..18ce1be2e68d1 100644 --- a/app/code/Magento/Theme/Model/Indexer/Design/Config/Plugin/Store.php +++ b/app/code/Magento/Theme/Model/Indexer/Design/Config/Plugin/Store.php @@ -6,7 +6,7 @@ namespace Magento\Theme\Model\Indexer\Design\Config\Plugin; use Magento\Framework\Indexer\IndexerRegistry; -use Magento\Store\Model\Store as StoreStore; +use Magento\Store\Model\Store as StoreModel; use Magento\Theme\Model\Data\Design\Config; class Store @@ -19,42 +19,41 @@ class Store /** * @param IndexerRegistry $indexerRegistry */ - public function __construct( - IndexerRegistry $indexerRegistry - ) { + public function __construct(IndexerRegistry $indexerRegistry) + { $this->indexerRegistry = $indexerRegistry; } /** * Invalidate design config grid indexer on store creation * - * @param StoreStore $subject - * @param \Closure $proceed - * @return StoreStore + * @param StoreModel $subject + * @param StoreModel $result + * @return StoreModel * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function aroundSave(StoreStore $subject, \Closure $proceed) + public function afterSave(StoreModel $subject, StoreModel $result) { - $isObjectNew = $subject->getId() == 0; - $result = $proceed(); - if ($isObjectNew) { + if ($result->isObjectNew()) { $this->indexerRegistry->get(Config::DESIGN_CONFIG_GRID_INDEXER_ID)->invalidate(); } + return $result; } /** * Invalidate design config grid indexer on store removal * - * @param StoreStore $subject - * @param StoreStore $result - * @return StoreStore + * @param StoreModel $subject + * @param StoreModel $result + * @return StoreModel * * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ - public function afterDelete(StoreStore $subject, $result) + public function afterDelete(StoreModel $subject, $result) { $this->indexerRegistry->get(Config::DESIGN_CONFIG_GRID_INDEXER_ID)->invalidate(); + return $result; } } diff --git a/app/code/Magento/Theme/Test/Mftf/Test/StoreFrontCheckNotificationMessageContainerTest.xml b/app/code/Magento/Theme/Test/Mftf/Test/StoreFrontCheckNotificationMessageContainerTest.xml new file mode 100644 index 0000000000000..c60385b768bf3 --- /dev/null +++ b/app/code/Magento/Theme/Test/Mftf/Test/StoreFrontCheckNotificationMessageContainerTest.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + /** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +--> + +<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd"> + <test name="StoreFrontCheckNotificationMessageContainerTest"> + <annotations> + <features value="Message container"/> + <stories value="Message container selector"/> + <title value="Check notification message container"/> + <description value="Check aria-atomic property on notification container message"/> + <severity value="AVERAGE"/> + <testCaseId value="MC-37339"/> + <group value="Theme"/> + </annotations> + <before> + <createData entity="SimpleProduct2" stepKey="simpleProduct"/> + <createData entity="SalesRuleSpecificCouponAndByPercent" stepKey="createSalesRule"/> + <createData entity="SimpleSalesRuleCoupon" stepKey="createCouponForCartPriceRule"> + <requiredEntity createDataKey="createSalesRule"/> + </createData> + </before> + <after> + <deleteData createDataKey="simpleProduct" stepKey="deleteProduct"/> + <deleteData createDataKey="createSalesRule" stepKey="deleteSalesRule"/> + </after> + + <actionGroup ref="AssertProductNameAndSkuInStorefrontProductPageByCustomAttributeUrlKeyActionGroup" stepKey="openProductPageAndVerifyProduct"> + <argument name="product" value="$$simpleProduct$$"/> + </actionGroup> + <actionGroup ref="StorefrontAddProductToCartWithQtyActionGroup" stepKey="addProductToTheCart"> + <argument name="productQty" value="1"/> + </actionGroup> + + <waitForElementVisible selector="{{StorefrontProductPageSection.alertMessage}}[aria-atomic=true]" stepKey="checkAddedToCartMessage"/> + + <actionGroup ref="GoToCheckoutFromMinicartActionGroup" stepKey="goToCheckoutFromMinicart"/> + <actionGroup ref="GuestCheckoutFillingShippingSectionActionGroup" stepKey="guestCheckoutFillingShipping"> + <argument name="shippingMethod" value="Flat Rate"/> + </actionGroup> + <actionGroup ref="StorefrontApplyDiscountCodeActionGroup" stepKey="applyCoupon"> + <argument name="discountCode" value="$createCouponForCartPriceRule.code$"/> + </actionGroup> + + <waitForElementVisible selector="{{DiscountSection.DiscountVerificationMsgWithAriaAtomicProperty}}" stepKey="checkCouponCodeApply"/> + </test> +</tests> diff --git a/app/code/Magento/Theme/Test/Unit/Model/Indexer/Design/Config/Plugin/StoreTest.php b/app/code/Magento/Theme/Test/Unit/Model/Indexer/Design/Config/Plugin/StoreTest.php index b61246cc7583f..1d48c0fe04e7a 100644 --- a/app/code/Magento/Theme/Test/Unit/Model/Indexer/Design/Config/Plugin/StoreTest.php +++ b/app/code/Magento/Theme/Test/Unit/Model/Indexer/Design/Config/Plugin/StoreTest.php @@ -9,6 +9,7 @@ use Magento\Framework\Indexer\IndexerInterface; use Magento\Framework\Indexer\IndexerRegistry; +use Magento\Store\Model\Store as StoreModel; use Magento\Theme\Model\Data\Design\Config; use Magento\Theme\Model\Indexer\Design\Config\Plugin\Store; use PHPUnit\Framework\MockObject\MockObject; @@ -17,10 +18,10 @@ class StoreTest extends TestCase { /** @var Store */ - protected $model; + private $model; /** @var IndexerRegistry|MockObject */ - protected $indexerRegistryMock; + private $indexerRegistryMock; protected function setUp(): void { @@ -31,21 +32,15 @@ protected function setUp(): void $this->model = new Store($this->indexerRegistryMock); } - public function testAroundSave() + public function testAfterSave(): void { - $subjectId = 0; - - /** @var \Magento\Store\Model\Store|MockObject $subjectMock */ - $subjectMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + /** @var StoreModel|MockObject $subjectMock */ + $subjectMock = $this->getMockBuilder(StoreModel::class) ->disableOriginalConstructor() ->getMock(); $subjectMock->expects($this->once()) - ->method('getId') - ->willReturn($subjectId); - - $closureMock = function () use ($subjectMock) { - return $subjectMock; - }; + ->method('isObjectNew') + ->willReturn(true); /** @var IndexerInterface|MockObject $indexerMock */ $indexerMock = $this->getMockBuilder(IndexerInterface::class) @@ -58,35 +53,29 @@ public function testAroundSave() ->with(Config::DESIGN_CONFIG_GRID_INDEXER_ID) ->willReturn($indexerMock); - $this->assertEquals($subjectMock, $this->model->aroundSave($subjectMock, $closureMock)); + $this->assertSame($subjectMock, $this->model->afterSave($subjectMock, $subjectMock)); } - public function testAroundSaveWithExistentSubject() + public function testAfterSaveWithExistentSubject(): void { - $subjectId = 1; - - /** @var \Magento\Store\Model\Store|MockObject $subjectMock */ - $subjectMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + /** @var StoreModel|MockObject $subjectMock */ + $subjectMock = $this->getMockBuilder(StoreModel::class) ->disableOriginalConstructor() ->getMock(); $subjectMock->expects($this->once()) - ->method('getId') - ->willReturn($subjectId); - - $closureMock = function () use ($subjectMock) { - return $subjectMock; - }; + ->method('isObjectNew') + ->willReturn(false); $this->indexerRegistryMock->expects($this->never()) ->method('get'); - $this->assertEquals($subjectMock, $this->model->aroundSave($subjectMock, $closureMock)); + $this->assertSame($subjectMock, $this->model->afterSave($subjectMock, $subjectMock)); } - public function testAfterDelete() + public function testAfterDelete(): void { - /** @var \Magento\Store\Model\Store|MockObject $subjectMock */ - $subjectMock = $this->getMockBuilder(\Magento\Store\Model\Store::class) + /** @var StoreModel|MockObject $subjectMock */ + $subjectMock = $this->getMockBuilder(StoreModel::class) ->disableOriginalConstructor() ->getMock(); @@ -101,6 +90,6 @@ public function testAfterDelete() ->with(Config::DESIGN_CONFIG_GRID_INDEXER_ID) ->willReturn($indexerMock); - $this->assertEquals($subjectMock, $this->model->afterDelete($subjectMock, $subjectMock)); + $this->assertSame($subjectMock, $this->model->afterDelete($subjectMock, $subjectMock)); } } diff --git a/app/code/Magento/Theme/view/frontend/templates/messages.phtml b/app/code/Magento/Theme/view/frontend/templates/messages.phtml index 85e752635fb3a..f863da70e8987 100644 --- a/app/code/Magento/Theme/view/frontend/templates/messages.phtml +++ b/app/code/Magento/Theme/view/frontend/templates/messages.phtml @@ -6,7 +6,7 @@ ?> <div data-bind="scope: 'messages'"> <!-- ko if: cookieMessages && cookieMessages.length > 0 --> - <div role="alert" data-bind="foreach: { data: cookieMessages, as: 'message' }" class="messages"> + <div aria-atomic="true" role="alert" data-bind="foreach: { data: cookieMessages, as: 'message' }" class="messages"> <div data-bind="attr: { class: 'message-' + message.type + ' ' + message.type + ' message', 'data-ui-id': 'message-' + message.type @@ -17,7 +17,9 @@ <!-- /ko --> <!-- ko if: messages().messages && messages().messages.length > 0 --> - <div role="alert" data-bind="foreach: { data: messages().messages, as: 'message' }" class="messages"> + <div aria-atomic="true" role="alert" class="messages" data-bind="foreach: { + data: messages().messages, as: 'message' + }"> <div data-bind="attr: { class: 'message-' + message.type + ' ' + message.type + ' message', 'data-ui-id': 'message-' + message.type diff --git a/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml b/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml index 01b2101d3346c..ddb6c4071a0e7 100644 --- a/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml +++ b/app/code/Magento/Tinymce3/Test/Mftf/Test/AdminSwitchWYSIWYGOptionsTest.xml @@ -34,8 +34,7 @@ <selectOption selector="{{ContentManagementSection.Switcher}}" userInput="TinyMCE 4" stepKey="switchToVersion4" /> <click selector="{{ContentManagementSection.WYSIWYGOptions}}" stepKey="collapseWYSIWYGOptions1" /> <click selector="{{ContentManagementSection.Save}}" stepKey="clickSaveConfig1" /> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage1"/> - <waitForPageLoad stepKey="wait2"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage1"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle1"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab1" /> <waitForElementVisible selector="{{TinyMCESection.TinyMCE4}}" stepKey="waitForTinyMCE4"/> @@ -62,8 +61,7 @@ <selectOption selector="{{ContentManagementSection.Switcher}}" userInput="TinyMCE 3" stepKey="switchToVersion3" /> <click selector="{{ContentManagementSection.WYSIWYGOptions}}" stepKey="collapseWYSIWYGOptions2" /> <click selector="{{ContentManagementSection.Save}}" stepKey="clickSaveConfig2" /> - <amOnPage url="{{CmsNewPagePage.url}}" stepKey="navigateToPage2"/> - <waitForPageLoad stepKey="wait5"/> + <actionGroup ref="AdminOpenCreateNewCMSPageActionGroup" stepKey="navigateToPage2"/> <fillField selector="{{CmsNewPagePageBasicFieldsSection.pageTitle}}" userInput="{{_defaultCmsPage.title}}" stepKey="fillFieldTitle2"/> <click selector="{{CmsNewPagePageContentSection.header}}" stepKey="clickContentTab2" /> <comment userInput="removing deprecated element" stepKey="waitForTinyMCE3"/> diff --git a/app/code/Magento/Translation/view/frontend/requirejs-config.js b/app/code/Magento/Translation/view/frontend/requirejs-config.js index b4b3ce0f8c554..9a99d49eddbcf 100644 --- a/app/code/Magento/Translation/view/frontend/requirejs-config.js +++ b/app/code/Magento/Translation/view/frontend/requirejs-config.js @@ -10,8 +10,5 @@ var config = { addClass: 'Magento_Translation/js/add-class', 'Magento_Translation/add-class': 'Magento_Translation/js/add-class' } - }, - deps: [ - 'mage/translate-inline' - ] + } }; diff --git a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php index 27370cbfbd68c..9961fc41fc70d 100644 --- a/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php +++ b/app/code/Magento/Ui/Component/Form/Element/DataType/Media/OpenDialogUrl.php @@ -8,10 +8,8 @@ namespace Magento\Ui\Component\Form\Element\DataType\Media; -use Magento\Framework\DataObject; - /** - * Basic configuration for OdenDialogUrl + * Basic configuration for OpenDialogUrl */ class OpenDialogUrl { @@ -23,11 +21,11 @@ class OpenDialogUrl private $openDialogUrl; /** - * @param DataObject $url + * @param string $url */ - public function __construct(DataObject $url = null) + public function __construct(string $url = null) { - $this->openDialogUrl = $url; + $this->openDialogUrl = $url ?? self::DEFAULT_OPEN_DIALOG_URL; } /** @@ -37,9 +35,6 @@ public function __construct(DataObject $url = null) */ public function get(): string { - if ($this->openDialogUrl) { - return $this->openDialogUrl->getUrl(); - } - return self::DEFAULT_OPEN_DIALOG_URL; + return $this->openDialogUrl; } } diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js index 5f29c5982e094..0ac35df78e001 100644 --- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js +++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js @@ -1126,13 +1126,17 @@ define([ * Update whether value differs from default value */ setDifferedFromDefault: function () { - var recordData = utils.copy(this.recordData()); + var recordData; - Array.isArray(recordData) && recordData.forEach(function (item) { - delete item['record_id']; - }); + if (this.default) { + recordData = utils.copy(this.recordData()); + + Array.isArray(recordData) && recordData.forEach(function (item) { + delete item['record_id']; + }); - this.isDifferedFromDefault(!_.isEqual(recordData, this.default)); + this.isDifferedFromDefault(!_.isEqual(recordData, this.default)); + } }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/grid/columns/image-preview.js b/app/code/Magento/Ui/view/base/web/js/grid/columns/image-preview.js index d675bd7a60ab5..7dcf0994ef56b 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/columns/image-preview.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/columns/image-preview.js @@ -2,6 +2,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +/* eslint-disable no-undef */ define([ 'jquery', 'Magento_Ui/js/grid/columns/column', @@ -32,7 +33,8 @@ define([ listens: { '${ $.provider }:params.filters': 'hide', '${ $.provider }:params.search': 'hide', - '${ $.provider }:params.paging': 'hide' + '${ $.provider }:params.paging': 'hide', + '${ $.provider }:data.items': 'updateDisplayedRecord' }, exports: { height: '${ $.parentName }.thumbnail_url:previewHeight' @@ -48,6 +50,25 @@ define([ this._super(); $(document).on('keydown', this.handleKeyDown.bind(this)); + this.lastOpenedImage.subscribe(function (newValue) { + + if (newValue === false && _.isNull(this.visibleRecord())) { + return; + } + + if (newValue === this.visibleRecord()) { + return; + } + + if (newValue === false) { + this.hide(); + + return; + } + + this.show(this.masonry().rows()[newValue]); + }.bind(this)); + return this; }, @@ -128,8 +149,6 @@ define([ * @param {Object} record */ show: function (record) { - var img; - if (record._rowIndex === this.visibleRecord()) { this.hide(); @@ -141,9 +160,21 @@ define([ this._selectRow(record.rowNumber || null); this.visibleRecord(record._rowIndex); - img = $(this.previewImageSelector + ' img'); + this.lastOpenedImage(record._rowIndex); + this.updateImageData(); + }, - if (img.get(0).complete) { + /** + * Update image data when image preview is opened + */ + updateImageData: function () { + var img = $(this.previewImageSelector + ' img'); + + if (!img.get(0)) { + setTimeout(function () { + this.updateImageData(); + }.bind(this), 100); + } else if (img.get(0).complete) { this.updateHeight(); this.scrollToPreview(); } else { @@ -152,8 +183,17 @@ define([ this.scrollToPreview(); }.bind(this)); } + }, - this.lastOpenedImage(record._rowIndex); + /** + * Update preview displayed record data from the new items data if the preview is expanded + * + * @param {Array} items + */ + updateDisplayedRecord: function (items) { + if (!_.isNull(this.visibleRecord())) { + this.displayedRecord(items[this.visibleRecord()]); + } }, /** diff --git a/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js new file mode 100644 index 0000000000000..a913f3fa4a042 --- /dev/null +++ b/app/code/Magento/Ui/view/base/web/js/grid/filters/elements/ui-select.js @@ -0,0 +1,88 @@ +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +define([ + 'Magento_Ui/js/form/element/ui-select', + 'jquery', + 'underscore' +], function (Select, $, _) { + 'use strict'; + + return Select.extend({ + defaults: { + bookmarkProvider: 'ns = ${ $.ns }, index = bookmarks', + filterChipsProvider: 'componentType = filters, ns = ${ $.ns }', + validationUrl: false, + loadedOption: [], + validationLoading: true, + imports: { + activeIndex: '${ $.bookmarkProvider }:activeIndex' + }, + modules: { + filterChips: '${ $.filterChipsProvider }' + }, + listens: { + activeIndex: 'validateInitialValue' + } + + }, + + /** + * Initializes UiSelect component. + * + * @returns {UiSelect} Chainable. + */ + initialize: function () { + this._super(); + + this.validateInitialValue(); + + return this; + }, + + /** + * Validate initial value actually exists + */ + validateInitialValue: function () { + if (_.isEmpty(this.value())) { + this.validationLoading(false); + + return; + } + + $.ajax({ + url: this.validationUrl, + type: 'GET', + dataType: 'json', + context: this, + data: { + ids: this.value() + }, + + /** @param {Object} response */ + success: function (response) { + if (!_.isEmpty(response)) { + this.options([]); + this.success({ + options: response + }); + } + this.filterChips().updateActive(); + }, + + /** set empty array if error occurs */ + error: function () { + this.options([]); + }, + + /** stop loader */ + complete: function () { + this.validationLoading(false); + this.setCaption(); + } + }); + } + }); +}); diff --git a/app/code/Magento/Ui/view/base/web/js/grid/url-filter-applier.js b/app/code/Magento/Ui/view/base/web/js/grid/url-filter-applier.js index 1f870e9e819a1..be9044143c5a4 100644 --- a/app/code/Magento/Ui/view/base/web/js/grid/url-filter-applier.js +++ b/app/code/Magento/Ui/view/base/web/js/grid/url-filter-applier.js @@ -5,8 +5,9 @@ define([ 'uiComponent', - 'underscore' -], function (Component, _) { + 'underscore', + 'jquery' +], function (Component, _, $) { 'use strict'; return Component.extend({ @@ -36,7 +37,9 @@ define([ * Apply filter */ apply: function () { - var urlFilter = this.getFilterParam(this.searchString); + var urlFilter = this.getFilterParam(this.searchString), + applied, + filters; if (_.isUndefined(this.filterComponent())) { setTimeout(function () { @@ -47,8 +50,9 @@ define([ } if (Object.keys(urlFilter).length) { - this.filterComponent().setData(urlFilter, false); - this.filterComponent().apply(); + applied = this.filterComponent().get('applied'); + filters = $.extend({}, applied, urlFilter); + this.filterComponent().set('applied', filters); } }, diff --git a/app/code/Magento/Ui/view/frontend/web/template/messages.html b/app/code/Magento/Ui/view/frontend/web/template/messages.html index 0a8f672765b3c..c094d9d58bb75 100644 --- a/app/code/Magento/Ui/view/frontend/web/template/messages.html +++ b/app/code/Magento/Ui/view/frontend/web/template/messages.html @@ -6,12 +6,12 @@ --> <div data-role="checkout-messages" class="messages" data-bind="visible: isVisible(), click: removeAll"> <!-- ko foreach: messageContainer.getErrorMessages() --> - <div role="alert" class="message message-error error"> + <div aria-atomic="true" role="alert" class="message message-error error"> <div data-ui-id="checkout-cart-validationmessages-message-error" data-bind="text: $data"></div> </div> <!--/ko--> <!-- ko foreach: messageContainer.getSuccessMessages() --> - <div role="alert" class="message message-success success"> + <div aria-atomic="true" role="alert" class="message message-success success"> <div data-ui-id="checkout-cart-validationmessages-message-success" data-bind="text: $data"></div> </div> <!--/ko--> diff --git a/app/code/Magento/Wishlist/Controller/Shared/Allcart.php b/app/code/Magento/Wishlist/Controller/Shared/Allcart.php index 6300b14dcf515..89413eff8323f 100644 --- a/app/code/Magento/Wishlist/Controller/Shared/Allcart.php +++ b/app/code/Magento/Wishlist/Controller/Shared/Allcart.php @@ -3,13 +3,24 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + +declare(strict_types=1); + namespace Magento\Wishlist\Controller\Shared; +use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context; -use Magento\Wishlist\Model\ItemCarrier; +use Magento\Framework\App\Action\HttpGetActionInterface; +use Magento\Framework\App\Action\HttpPostActionInterface; +use Magento\Framework\Controller\Result\Forward; +use Magento\Framework\Controller\Result\Redirect; use Magento\Framework\Controller\ResultFactory; +use Magento\Wishlist\Model\ItemCarrier; -class Allcart extends \Magento\Framework\App\Action\Action +/** + * Wishlist Allcart Controller + */ +class Allcart extends Action implements HttpGetActionInterface, HttpPostActionInterface { /** * @var WishlistProvider @@ -17,7 +28,7 @@ class Allcart extends \Magento\Framework\App\Action\Action protected $wishlistProvider; /** - * @var \Magento\Wishlist\Model\ItemCarrier + * @var ItemCarrier */ protected $itemCarrier; @@ -39,21 +50,22 @@ public function __construct( /** * Add all items from wishlist to shopping cart * - * @return \Magento\Framework\Controller\ResultInterface + * {@inheritDoc} */ public function execute() { $wishlist = $this->wishlistProvider->getWishlist(); if (!$wishlist) { - /** @var \Magento\Framework\Controller\Result\Forward $resultForward */ + /** @var Forward $resultForward */ $resultForward = $this->resultFactory->create(ResultFactory::TYPE_FORWARD); $resultForward->forward('noroute'); return $resultForward; } $redirectUrl = $this->itemCarrier->moveAllToCart($wishlist, $this->getRequest()->getParam('qty')); - /** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */ + /** @var Redirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); $resultRedirect->setUrl($redirectUrl); + return $resultRedirect; } } diff --git a/app/code/Magento/Wishlist/Controller/Shared/Cart.php b/app/code/Magento/Wishlist/Controller/Shared/Cart.php index c0a394ce9d762..939cbe3a2c46f 100644 --- a/app/code/Magento/Wishlist/Controller/Shared/Cart.php +++ b/app/code/Magento/Wishlist/Controller/Shared/Cart.php @@ -13,7 +13,7 @@ use Magento\Framework\App\Action\Action; use Magento\Framework\App\Action\Context as ActionContext; use Magento\Framework\App\Action\HttpPostActionInterface; -use Magento\Framework\Controller\Result\Redirect; +use Magento\Framework\Controller\Result\Redirect as ResultRedirect; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Escaper; use Magento\Framework\Exception\LocalizedException; @@ -124,9 +124,11 @@ public function execute() } catch (\Exception $e) { $this->messageManager->addExceptionMessage($e, __('We can\'t add the item to the cart right now.')); } - /** @var Redirect $resultRedirect */ + + /** @var ResultRedirect $resultRedirect */ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT); $resultRedirect->setUrl($redirectUrl); + return $resultRedirect; } } diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml index 28a17d30aea2b..a2219d5145f17 100644 --- a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml +++ b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminDeleteCustomerWishListItemTest.xml @@ -23,7 +23,6 @@ <createData entity="SimpleProduct" stepKey="createProduct"> <requiredEntity createDataKey="createCategory"/> </createData> - <magentoCLI command="cron:run --group=index" stepKey="runCronIndexer"/> <createData entity="Simple_US_Customer" stepKey="createCustomer"/> </before> <after> diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Shared/AllcartTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Shared/AllcartTest.php index eea3346e8e81b..d9339af8144f4 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Shared/AllcartTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Shared/AllcartTest.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ + declare(strict_types=1); namespace Magento\Wishlist\Test\Unit\Controller\Shared; @@ -20,83 +21,60 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +/** + * Test for \Magento\Wishlist\Controller\Shared\Allcart. + */ class AllcartTest extends TestCase { /** * @var Allcart */ - protected $allcartController; - - /** - * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager - */ - protected $objectManagerHelper; - - /** - * @var Context - */ - protected $context; + private $allcartController; /** * @var WishlistProvider|MockObject */ - protected $wishlistProviderMock; + private $wishlistProviderMock; /** * @var ItemCarrier|MockObject */ - protected $itemCarrierMock; + private $itemCarrierMock; /** * @var Wishlist|MockObject */ - protected $wishlistMock; + private $wishlistMock; /** * @var Http|MockObject */ - protected $requestMock; - - /** - * @var ResultFactory|MockObject - */ - protected $resultFactoryMock; + private $requestMock; /** * @var Redirect|MockObject */ - protected $resultRedirectMock; + private $resultRedirectMock; /** * @var Forward|MockObject */ - protected $resultForwardMock; + private $resultForwardMock; + /** + * @inheritDoc + */ protected function setUp(): void { - $this->wishlistProviderMock = $this->getMockBuilder(WishlistProvider::class) - ->disableOriginalConstructor() - ->getMock(); - $this->itemCarrierMock = $this->getMockBuilder(ItemCarrier::class) - ->disableOriginalConstructor() - ->getMock(); - $this->wishlistMock = $this->getMockBuilder(Wishlist::class) - ->disableOriginalConstructor() - ->getMock(); - $this->requestMock = $this->getMockBuilder(Http::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resultFactoryMock = $this->getMockBuilder(ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resultRedirectMock = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resultForwardMock = $this->getMockBuilder(Forward::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->resultFactoryMock->expects($this->any()) + $this->wishlistProviderMock = $this->createMock(WishlistProvider::class); + $this->itemCarrierMock = $this->createMock(ItemCarrier::class); + $this->wishlistMock = $this->createMock(Wishlist::class); + $this->requestMock = $this->createMock(Http::class); + $resultFactoryMock = $this->createMock(ResultFactory::class); + $this->resultRedirectMock = $this->createMock(Redirect::class); + $this->resultForwardMock = $this->createMock(Forward::class); + + $resultFactoryMock->expects($this->any()) ->method('create') ->willReturnMap( [ @@ -105,18 +83,18 @@ protected function setUp(): void ] ); - $this->objectManagerHelper = new ObjectManagerHelper($this); - $this->context = $this->objectManagerHelper->getObject( + $objectManagerHelper = new ObjectManagerHelper($this); + $context = $objectManagerHelper->getObject( Context::class, [ 'request' => $this->requestMock, - 'resultFactory' => $this->resultFactoryMock + 'resultFactory' => $resultFactoryMock ] ); - $this->allcartController = $this->objectManagerHelper->getObject( + $this->allcartController = $objectManagerHelper->getObject( Allcart::class, [ - 'context' => $this->context, + 'context' => $context, 'wishlistProvider' => $this->wishlistProviderMock, 'itemCarrier' => $this->itemCarrierMock ] diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Shared/CartTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Shared/CartTest.php index 923b33ef4748b..e6a127457a6c6 100644 --- a/app/code/Magento/Wishlist/Test/Unit/Controller/Shared/CartTest.php +++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Shared/CartTest.php @@ -8,6 +8,7 @@ namespace Magento\Wishlist\Test\Unit\Controller\Shared; use Magento\Catalog\Model\Product; +use Magento\Catalog\Model\Product\Exception; use Magento\Checkout\Helper\Cart as CartHelper; use Magento\Checkout\Model\Cart; use Magento\Framework\App\Action\Context as ActionContext; @@ -29,156 +30,146 @@ use PHPUnit\Framework\TestCase; /** + * Test for \Magento\Wishlist\Controller\Shared\Cart. + * * @SuppressWarnings(PHPMD.TooManyFields) * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class CartTest extends TestCase { - /** @var SharedCart|MockObject */ - protected $model; - - /** @var RequestInterface|MockObject */ - protected $request; - - /** @var ManagerInterface|MockObject */ - protected $messageManager; - - /** @var ActionContext|MockObject */ - protected $context; - - /** @var Cart|MockObject */ - protected $cart; + /** + * @var SharedCart|MockObject + */ + private $model; - /** @var CartHelper|MockObject */ - protected $cartHelper; + /** + * @var RequestInterface|MockObject + */ + private $request; - /** @var Quote|MockObject */ - protected $quote; + /** + * @var ManagerInterface|MockObject + */ + private $messageManager; - /** @var OptionCollection|MockObject */ - protected $optionCollection; + /** + * @var Cart|MockObject + */ + private $cart; - /** @var OptionFactory|MockObject */ - protected $optionFactory; + /** + * @var CartHelper|MockObject + */ + private $cartHelper; - /** @var Option|MockObject */ - protected $option; + /** + * @var Quote|MockObject + */ + private $quote; - /** @var ItemFactory|MockObject */ - protected $itemFactory; + /** + * @var OptionCollection|MockObject + */ + private $optionCollection; - /** @var Item|MockObject */ - protected $item; + /** + * @var Option|MockObject + */ + private $option; - /** @var Escaper|MockObject */ - protected $escaper; + /** + * @var Item|MockObject + */ + private $item; - /** @var RedirectInterface|MockObject */ - protected $redirect; + /** + * @var Escaper|MockObject + */ + private $escaper; - /** @var ResultFactory|MockObject */ - protected $resultFactory; + /** + * @var RedirectInterface|MockObject + */ + private $redirect; - /** @var Redirect|MockObject */ - protected $resultRedirect; + /** + * @var Redirect|MockObject + */ + private $resultRedirect; - /** @var Product|MockObject */ - protected $product; + /** + * @var Product|MockObject + */ + private $product; + /** + * @inheritDoc + */ protected function setUp(): void { - $this->request = $this->getMockBuilder(RequestInterface::class) - ->getMockForAbstractClass(); - - $this->redirect = $this->getMockBuilder(RedirectInterface::class) - ->getMockForAbstractClass(); - - $this->messageManager = $this->getMockBuilder(ManagerInterface::class) - ->getMockForAbstractClass(); - - $this->resultRedirect = $this->getMockBuilder(Redirect::class) - ->disableOriginalConstructor() - ->getMock(); + $this->request = $this->getMockForAbstractClass(RequestInterface::class); + $this->redirect = $this->getMockForAbstractClass(RedirectInterface::class); + $this->messageManager = $this->getMockForAbstractClass(ManagerInterface::class); + $this->resultRedirect = $this->createMock(Redirect::class); - $this->resultFactory = $this->getMockBuilder(ResultFactory::class) - ->disableOriginalConstructor() - ->getMock(); - $this->resultFactory->expects($this->once()) + $resultFactory = $this->createMock(ResultFactory::class); + $resultFactory->expects($this->once()) ->method('create') ->with(ResultFactory::TYPE_REDIRECT) ->willReturn($this->resultRedirect); - $this->context = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class) + /** @var ActionContext|MockObject $context */ + $context = $this->getMockBuilder(ActionContext::class) ->disableOriginalConstructor() ->getMock(); - $this->context->expects($this->any()) + $context->expects($this->any()) ->method('getRequest') ->willReturn($this->request); - $this->context->expects($this->any()) + $context->expects($this->any()) ->method('getRedirect') ->willReturn($this->redirect); - $this->context->expects($this->any()) + $context->expects($this->any()) ->method('getMessageManager') ->willReturn($this->messageManager); - $this->context->expects($this->any()) + $context->expects($this->any()) ->method('getResultFactory') - ->willReturn($this->resultFactory); - - $this->cart = $this->getMockBuilder(\Magento\Checkout\Model\Cart::class) - ->disableOriginalConstructor() - ->getMock(); + ->willReturn($resultFactory); - $this->cartHelper = $this->getMockBuilder(\Magento\Checkout\Helper\Cart::class) - ->disableOriginalConstructor() - ->getMock(); + $this->cart = $this->createMock(Cart::class); + $this->cartHelper = $this->createMock(CartHelper::class); $this->quote = $this->getMockBuilder(Quote::class) ->disableOriginalConstructor() - ->setMethods(['getHasError']) + ->addMethods(['getHasError']) ->getMock(); - $this->optionCollection = $this->getMockBuilder( - \Magento\Wishlist\Model\ResourceModel\Item\Option\Collection::class - )->disableOriginalConstructor() - ->getMock(); + $this->optionCollection = $this->createMock(OptionCollection::class); $this->option = $this->getMockBuilder(Option::class) ->disableOriginalConstructor() ->getMock(); - $this->optionFactory = $this->getMockBuilder(OptionFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->optionFactory->expects($this->once()) + /** @var OptionFactory|MockObject $optionFactory */ + $optionFactory = $this->createMock(OptionFactory::class); + $optionFactory->expects($this->once()) ->method('create') ->willReturn($this->option); - $this->item = $this->getMockBuilder(Item::class) - ->disableOriginalConstructor() - ->getMock(); + $this->item = $this->createMock(Item::class); - $this->itemFactory = $this->getMockBuilder(ItemFactory::class) - ->disableOriginalConstructor() - ->setMethods(['create']) - ->getMock(); - $this->itemFactory->expects($this->once()) + $itemFactory = $this->createMock(ItemFactory::class); + $itemFactory->expects($this->once()) ->method('create') ->willReturn($this->item); - $this->escaper = $this->getMockBuilder(Escaper::class) - ->disableOriginalConstructor() - ->getMock(); - - $this->product = $this->getMockBuilder(Product::class) - ->disableOriginalConstructor() - ->getMock(); + $this->escaper = $this->createMock(Escaper::class); + $this->product = $this->createMock(Product::class); $this->model = new SharedCart( - $this->context, + $context, $this->cart, - $this->optionFactory, - $this->itemFactory, + $optionFactory, + $itemFactory, $this->cartHelper, $this->escaper ); @@ -358,7 +349,7 @@ public function testExecuteProductException() $this->option->expects($this->once()) ->method('getCollection') - ->willThrowException(new \Magento\Catalog\Model\Product\Exception(__('LocalizedException'))); + ->willThrowException(new Exception(__('LocalizedException'))); $this->resultRedirect->expects($this->once()) ->method('setUrl') diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/AddProductsToWishlist.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/AddProductsToWishlist.php index 3489585cd17d7..840c4638614c4 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/AddProductsToWishlist.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/AddProductsToWishlist.php @@ -83,7 +83,7 @@ public function resolve( array $args = null ) { if (!$this->wishlistConfig->isEnabled()) { - throw new GraphQlInputException(__('The wishlist is not currently available.')); + throw new GraphQlInputException(__('The wishlist configuration is currently disabled.')); } $customerId = $context->getUserId(); diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlistResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlistResolver.php index cad574ef56ed2..b73afe27883dd 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlistResolver.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlistResolver.php @@ -54,7 +54,7 @@ public function resolve( array $args = null ) { if (!$this->wishlistConfig->isEnabled()) { - throw new GraphQlInputException(__('The wishlist is not currently available.')); + throw new GraphQlInputException(__('The wishlist configuration is currently disabled.')); } if (false === $context->getExtensionAttributes()->getIsCustomer()) { diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlists.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlists.php new file mode 100644 index 0000000000000..ad0c73691720a --- /dev/null +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/CustomerWishlists.php @@ -0,0 +1,102 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\WishlistGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Wishlist\Model\ResourceModel\Wishlist\Collection as WishlistCollection; +use Magento\Wishlist\Model\ResourceModel\Wishlist\CollectionFactory as WishlistCollectionFactory; +use Magento\Wishlist\Model\Wishlist; +use Magento\Wishlist\Model\Wishlist\Config as WishlistConfig; +use Magento\WishlistGraphQl\Mapper\WishlistDataMapper; + +/** + * Fetches customer wishlist list + */ +class CustomerWishlists implements ResolverInterface +{ + /** + * @var WishlistDataMapper + */ + private $wishlistDataMapper; + + /** + * @var WishlistConfig + */ + private $wishlistConfig; + + /** + * @var WishlistCollectionFactory + */ + private $wishlistCollectionFactory; + + /** + * @param WishlistDataMapper $wishlistDataMapper + * @param WishlistConfig $wishlistConfig + * @param WishlistCollectionFactory $wishlistCollectionFactory + */ + public function __construct( + WishlistDataMapper $wishlistDataMapper, + WishlistConfig $wishlistConfig, + WishlistCollectionFactory $wishlistCollectionFactory + ) { + $this->wishlistDataMapper = $wishlistDataMapper; + $this->wishlistConfig = $wishlistConfig; + $this->wishlistCollectionFactory = $wishlistCollectionFactory; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$this->wishlistConfig->isEnabled()) { + throw new GraphQlInputException(__('The wishlist configuration is currently disabled.')); + } + + $customerId = $context->getUserId(); + + if (null === $customerId || 0 === $customerId) { + throw new GraphQlAuthorizationException( + __('The current user cannot perform operations on wishlist') + ); + } + + $currentPage = $args['currentPage'] ?? 1; + $pageSize = $args['pageSize'] ?? 20; + + /** @var WishlistCollection $collection */ + $collection = $this->wishlistCollectionFactory->create(); + $collection->filterByCustomerId($customerId); + + if ($currentPage > 0) { + $collection->setCurPage($currentPage); + } + + if ($pageSize > 0) { + $collection->setPageSize($pageSize); + } + + $wishlists = []; + + /** @var Wishlist $wishList */ + foreach ($collection->getItems() as $wishList) { + array_push($wishlists, $this->wishlistDataMapper->map($wishList)); + } + + return $wishlists; + } +} diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/ProductResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/ProductResolver.php index 65c8498fc89ad..31dd33ff2cd79 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/ProductResolver.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/ProductResolver.php @@ -7,12 +7,12 @@ namespace Magento\WishlistGraphQl\Model\Resolver; +use Magento\Catalog\Model\Product; use Magento\CatalogGraphQl\Model\ProductDataProvider; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; -use Magento\Wishlist\Model\Item; /** * Fetches the Product data according to the GraphQL schema @@ -45,9 +45,9 @@ public function resolve( if (!isset($value['model'])) { throw new LocalizedException(__('Missing key "model" in Wishlist Item value data')); } - /** @var Item $wishlistItem */ - $wishlistItem = $value['model']; + /** @var Product $product */ + $product = $value['model']; - return $this->productDataProvider->getProductDataById((int)$wishlistItem->getProductId()); + return $this->productDataProvider->getProductDataById((int) $product->getId()); } } diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/RemoveProductsFromWishlist.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/RemoveProductsFromWishlist.php index a59c5ccdb0f70..66a6c7b86ea37 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/RemoveProductsFromWishlist.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/RemoveProductsFromWishlist.php @@ -83,7 +83,7 @@ public function resolve( array $args = null ) { if (!$this->wishlistConfig->isEnabled()) { - throw new GraphQlInputException(__('The wishlist is not currently available.')); + throw new GraphQlInputException(__('The wishlist configuration is currently disabled.')); } $customerId = $context->getUserId(); diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/Type/WishlistItemType.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/Type/WishlistItemType.php new file mode 100644 index 0000000000000..ae4a6ed2b6a64 --- /dev/null +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/Type/WishlistItemType.php @@ -0,0 +1,59 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\WishlistGraphQl\Model\Resolver\Type; + +use Magento\Catalog\Api\Data\ProductInterface; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; + +/** + * Resolving the wishlist item type + */ +class WishlistItemType implements TypeResolverInterface +{ + /** + * @var array + */ + private $supportedTypes = []; + + /** + * @param array $supportedTypes + */ + public function __construct(array $supportedTypes = []) + { + $this->supportedTypes = $supportedTypes; + } + + /** + * Resolving wishlist item type + * + * @param array $data + * + * @return string + * + * @throws LocalizedException + */ + public function resolveType(array $data): string + { + if (!$data['model'] instanceof ProductInterface) { + throw new LocalizedException(__('"model" should be a "%instance" instance', [ + 'instance' => ProductInterface::class + ])); + } + + $productTypeId = $data['model']->getTypeId(); + + if (!isset($this->supportedTypes[$productTypeId])) { + throw new LocalizedException( + __('Product "%product_type" type is not supported', ['product_type' => $productTypeId]) + ); + } + + return $this->supportedTypes[$productTypeId]; + } +} diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/UpdateProductsInWishlist.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/UpdateProductsInWishlist.php index c6ede66fc2b1b..47a408d55555b 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/UpdateProductsInWishlist.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/UpdateProductsInWishlist.php @@ -83,7 +83,7 @@ public function resolve( array $args = null ) { if (!$this->wishlistConfig->isEnabled()) { - throw new GraphQlInputException(__('The wishlist is not currently available.')); + throw new GraphQlInputException(__('The wishlist configuration is currently disabled.')); } $customerId = $context->getUserId(); diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistById.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistById.php new file mode 100644 index 0000000000000..1ddf91637fe90 --- /dev/null +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistById.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\WishlistGraphQl\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Exception\GraphQlInputException; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Wishlist\Model\ResourceModel\Wishlist as WishlistResourceModel; +use Magento\Wishlist\Model\Wishlist; +use Magento\Wishlist\Model\Wishlist\Config as WishlistConfig; +use Magento\Wishlist\Model\WishlistFactory; +use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; +use Magento\WishlistGraphQl\Mapper\WishlistDataMapper; + +/** + * Fetches the Wishlist data by ID according to the GraphQL schema + */ +class WishlistById implements ResolverInterface +{ + /** + * @var WishlistResourceModel + */ + private $wishlistResource; + + /** + * @var WishlistFactory + */ + private $wishlistFactory; + + /** + * @var WishlistDataMapper + */ + private $wishlistDataMapper; + + /** + * @var WishlistConfig + */ + private $wishlistConfig; + + /** + * @param WishlistResourceModel $wishlistResource + * @param WishlistFactory $wishlistFactory + * @param WishlistDataMapper $wishlistDataMapper + * @param WishlistConfig $wishlistConfig + */ + public function __construct( + WishlistResourceModel $wishlistResource, + WishlistFactory $wishlistFactory, + WishlistDataMapper $wishlistDataMapper, + WishlistConfig $wishlistConfig + ) { + $this->wishlistResource = $wishlistResource; + $this->wishlistFactory = $wishlistFactory; + $this->wishlistDataMapper = $wishlistDataMapper; + $this->wishlistConfig = $wishlistConfig; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!$this->wishlistConfig->isEnabled()) { + throw new GraphQlInputException(__('The wishlist configuration is currently disabled.')); + } + + $customerId = $context->getUserId(); + + if (null === $customerId || 0 === $customerId) { + throw new GraphQlAuthorizationException( + __('The current user cannot perform operations on wishlist') + ); + } + + $wishlist = $this->getWishlist((int) $args['id'], $customerId); + + if (null === $wishlist->getId() || (int) $wishlist->getCustomerId() !== $customerId) { + return []; + } + + return $this->wishlistDataMapper->map($wishlist); + } + + /** + * Get wishlist + * + * @param int $wishlistId + * @param int $customerId + * + * @return Wishlist + */ + private function getWishlist(int $wishlistId, int $customerId): Wishlist + { + $wishlist = $this->wishlistFactory->create(); + + if ($wishlistId > 0) { + $this->wishlistResource->load($wishlist, $wishlistId); + } else { + $this->wishlistResource->load($wishlist, $customerId, 'customer_id'); + } + + return $wishlist; + } +} diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistItems.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistItems.php new file mode 100644 index 0000000000000..77ff483a60bd2 --- /dev/null +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistItems.php @@ -0,0 +1,98 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\WishlistGraphQl\Model\Resolver; + +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; +use Magento\Store\Api\Data\StoreInterface; +use Magento\Store\Model\StoreManagerInterface; +use Magento\Wishlist\Model\ResourceModel\Item\Collection as WishlistItemCollection; +use Magento\Wishlist\Model\ResourceModel\Item\CollectionFactory as WishlistItemCollectionFactory; +use Magento\Wishlist\Model\Item; +use Magento\Wishlist\Model\Wishlist; + +/** + * Fetches the Wishlist Items data according to the GraphQL schema + */ +class WishlistItems implements ResolverInterface +{ + /** + * @var WishlistItemCollectionFactory + */ + private $wishlistItemCollectionFactory; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @param WishlistItemCollectionFactory $wishlistItemCollectionFactory + * @param StoreManagerInterface $storeManager + */ + public function __construct( + WishlistItemCollectionFactory $wishlistItemCollectionFactory, + StoreManagerInterface $storeManager + ) { + $this->wishlistItemCollectionFactory = $wishlistItemCollectionFactory; + $this->storeManager = $storeManager; + } + + /** + * @inheritdoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + if (!isset($value['model'])) { + throw new LocalizedException(__('Missing key "model" in Wishlist value data')); + } + /** @var Wishlist $wishlist */ + $wishlist = $value['model']; + + $wishlistItems = $this->getWishListItems($wishlist); + + $data = []; + foreach ($wishlistItems as $wishlistItem) { + $data[] = [ + 'id' => $wishlistItem->getId(), + 'quantity' => $wishlistItem->getData('qty'), + 'description' => $wishlistItem->getDescription(), + 'added_at' => $wishlistItem->getAddedAt(), + 'model' => $wishlistItem->getProduct(), + 'itemModel' => $wishlistItem, + ]; + } + return $data; + } + + /** + * Get wishlist items + * + * @param Wishlist $wishlist + * @return Item[] + */ + private function getWishListItems(Wishlist $wishlist): array + { + /** @var WishlistItemCollection $wishlistItemCollection */ + $wishlistItemCollection = $this->wishlistItemCollectionFactory->create(); + $wishlistItemCollection + ->addWishlistFilter($wishlist) + ->addStoreFilter(array_map(function (StoreInterface $store) { + return $store->getId(); + }, $this->storeManager->getStores())) + ->setVisibilityFilter(); + return $wishlistItemCollection->getItems(); + } +} diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistItemsResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistItemsResolver.php index dfbbf6543f66f..36a03da2b79a9 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistItemsResolver.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistItemsResolver.php @@ -70,7 +70,7 @@ public function resolve( 'qty' => $wishlistItem->getData('qty'), 'description' => $wishlistItem->getDescription(), 'added_at' => $wishlistItem->getAddedAt(), - 'model' => $wishlistItem, + 'model' => $wishlistItem->getProduct(), ]; } return $data; diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php index 09c0a8a935a6c..f31b403a514fb 100644 --- a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php +++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php @@ -63,7 +63,7 @@ public function resolve( array $args = null ) { if (!$this->wishlistConfig->isEnabled()) { - throw new GraphQlInputException(__('The wishlist is not currently available.')); + throw new GraphQlInputException(__('The wishlist configuration is currently disabled.')); } $customerId = $context->getUserId(); diff --git a/app/code/Magento/WishlistGraphQl/composer.json b/app/code/Magento/WishlistGraphQl/composer.json index 7a3fca599a4b3..58bc738bd24d6 100644 --- a/app/code/Magento/WishlistGraphQl/composer.json +++ b/app/code/Magento/WishlistGraphQl/composer.json @@ -5,6 +5,7 @@ "require": { "php": "~7.3.0||~7.4.0", "magento/framework": "*", + "magento/module-catalog": "*", "magento/module-catalog-graph-ql": "*", "magento/module-wishlist": "*", "magento/module-store": "*" diff --git a/app/code/Magento/WishlistGraphQl/etc/schema.graphqls b/app/code/Magento/WishlistGraphQl/etc/schema.graphqls index 430e77cc45e96..69bc45462d4c8 100644 --- a/app/code/Magento/WishlistGraphQl/etc/schema.graphqls +++ b/app/code/Magento/WishlistGraphQl/etc/schema.graphqls @@ -6,7 +6,12 @@ type Query { } type Customer { - wishlist: Wishlist! @resolver(class:"\\Magento\\WishlistGraphQl\\Model\\Resolver\\CustomerWishlistResolver") @doc(description: "Contains the contents of a customer's wish lists") @cache(cacheable: false) + wishlists( + pageSize: Int = 20 @doc(description: "Specifies the maximum number of results to return at once. This attribute is optional."), + currentPage: Int = 1 @doc(description: "Specifies which page of results to return. The default value is 1.") + ): [Wishlist!]! @doc(description: "An array of wishlists. In Magento Open Source, customers are limited to one wish list. The number of wish lists is configurable for Magento Commerce") @resolver(class:"\\Magento\\WishlistGraphQl\\Model\\Resolver\\CustomerWishlists") + wishlist: Wishlist! @deprecated(reason: "Use `Customer.wishlists` or `Customer.wishlist_v2`") @resolver(class:"\\Magento\\WishlistGraphQl\\Model\\Resolver\\CustomerWishlistResolver") @doc(description: "Contains a customer's wish lists") @cache(cacheable: false) + wishlist_v2(id: ID!): Wishlist @doc(description: "Retrieve the specified wish list") @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\WishlistById") } type WishlistOutput @doc(description: "Deprecated: `Wishlist` type should be used instead") { @@ -19,12 +24,22 @@ type WishlistOutput @doc(description: "Deprecated: `Wishlist` type should be use type Wishlist { id: ID @doc(description: "Wishlist unique identifier") - items: [WishlistItem] @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\WishlistItemsResolver") @doc(description: "An array of items in the customer's wish list"), - items_count: Int @doc(description: "The number of items in the wish list"), - sharing_code: String @doc(description: "An encrypted code that Magento uses to link to the wish list"), + items: [WishlistItem] @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\WishlistItemsResolver") @deprecated(reason: "Use field `items_v2` from type `Wishlist` instead") + items_v2: [WishlistItemInterface] @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\WishlistItems") @doc(description: "An array of items in the customer's wish list") + items_count: Int @doc(description: "The number of items in the wish list") + sharing_code: String @doc(description: "An encrypted code that Magento uses to link to the wish list") updated_at: String @doc(description: "The time of the last modification to the wish list") } +interface WishlistItemInterface @typeResolver(class: "Magento\\WishlistGraphQl\\Model\\Resolver\\Type\\WishlistItemType") { + id: ID! @doc(description: "The ID of the wish list item") + quantity: Float! @doc(description: "The quantity of this wish list item") + description: String @doc(description: "The description of the item") + added_at: String! @doc(description: "The date and time the item was added to the wish list") + product: ProductInterface @doc(description: "Product details of the wish list item") @resolver(class: "\\Magento\\WishlistGraphQl\\Model\\Resolver\\ProductResolver") + customizable_options: [SelectedCustomizableOption] @doc(description: "Custom options selected for the wish list item") +} + type WishlistItem { id: Int @doc(description: "The wish list item ID") qty: Float @doc(description: "The quantity of this wish list item"), diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_image-uploader.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_image-uploader.less index f187697281252..a9172d5164c38 100644 --- a/app/design/adminhtml/Magento/backend/web/css/source/components/_image-uploader.less +++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_image-uploader.less @@ -9,8 +9,8 @@ .image-uploader { .image-upload-requirements { - margin-top: 8px; font-size: .9em; + margin-top: 8px; } .image-placeholder { @@ -19,12 +19,12 @@ } .image-uploader-spinner { - width: 50%; - height: 50%; background-size: auto; + height: 50%; margin: 0; - transform: translate(50%, 50%); position: absolute; + transform: translate(50%, 50%); + width: 50%; } .image-uploader-preview { @@ -33,7 +33,10 @@ .image-uploader-preview-link, .image-uploader-preview-link .preview-image { + display: block; height: inherit; + margin-left: auto; + margin-right: auto; } } diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout-agreements.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout-agreements.less index ff4f07a983940..b8be2b33a4475 100644 --- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout-agreements.less +++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_checkout-agreements.less @@ -13,6 +13,30 @@ margin-bottom: @indent__base; } + .checkout-agreement.field { + .lib-vendor-prefix-display(); + + &.required { + label:after { + content: none; + } + + .action-show { + &:after { + content: '*'; + .lib-typography( + @_font-size: @form-field-label-asterisk__font-size, + @_color: @form-field-label-asterisk__color, + @_font-family: @form-field-label-asterisk__font-family, + @_font-weight: @form-field-label-asterisk__font-weight, + @_line-height: @form-field-label-asterisk__line-height, + @_font-style: @form-field-label-asterisk__font-style + ); + } + } + } + } + .action-show { &:extend(.abs-action-button-as-link all); vertical-align: baseline; diff --git a/app/design/frontend/Magento/blank/web/css/source/_extends.less b/app/design/frontend/Magento/blank/web/css/source/_extends.less index 5bdaa4c3c35a3..690b89f42b419 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_extends.less +++ b/app/design/frontend/Magento/blank/web/css/source/_extends.less @@ -1110,7 +1110,7 @@ .abs-shopping-cart-items { .action { &.continue { - border-radius: 3px; + border-radius: @button__border-radius; font-weight: @font-weight__bold; .lib-link-as-button(); .lib-button( diff --git a/app/design/frontend/Magento/blank/web/css/source/_icons.less b/app/design/frontend/Magento/blank/web/css/source/_icons.less index 7d1ceaca73c72..5cd7795aa506c 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_icons.less +++ b/app/design/frontend/Magento/blank/web/css/source/_icons.less @@ -8,6 +8,7 @@ @family-name: @icons__font-name, @font-path: @icons__font-path, @font-weight: normal, - @font-style: normal + @font-style: normal, + @font-display: block ); } diff --git a/app/design/frontend/Magento/blank/web/css/source/_typography.less b/app/design/frontend/Magento/blank/web/css/source/_typography.less index 6807c0f692af8..02ccd90d4655d 100644 --- a/app/design/frontend/Magento/blank/web/css/source/_typography.less +++ b/app/design/frontend/Magento/blank/web/css/source/_typography.less @@ -9,7 +9,7 @@ & when (@media-common = true) { .lib-font-face( - @family-name: @font-family-name__base, + @family-name: 'Open Sans', @font-path: '@{baseDir}fonts/opensans/light/opensans-300', @font-weight: 300, @font-style: normal, @@ -17,7 +17,7 @@ ); .lib-font-face( - @family-name: @font-family-name__base, + @family-name: 'Open Sans', @font-path: '@{baseDir}fonts/opensans/regular/opensans-400', @font-weight: 400, @font-style: normal, @@ -25,7 +25,7 @@ ); .lib-font-face( - @family-name: @font-family-name__base, + @family-name: 'Open Sans', @font-path: '@{baseDir}fonts/opensans/semibold/opensans-600', @font-weight: 600, @font-style: normal, @@ -33,7 +33,7 @@ ); .lib-font-face( - @family-name: @font-family-name__base, + @family-name: 'Open Sans', @font-path: '@{baseDir}fonts/opensans/bold/opensans-700', @font-weight: 700, @font-style: normal, diff --git a/app/etc/di.xml b/app/etc/di.xml index fed2e336046f9..585c88f68ff6f 100644 --- a/app/etc/di.xml +++ b/app/etc/di.xml @@ -185,6 +185,7 @@ <preference for="Magento\Framework\Setup\Declaration\Schema\Db\DbSchemaWriterInterface" type="Magento\Framework\Setup\Declaration\Schema\Db\MySQL\DbSchemaWriter" /> <preference for="Magento\Framework\Setup\Declaration\Schema\SchemaConfigInterface" type="Magento\Framework\Setup\Declaration\Schema\SchemaConfig" /> <preference for="Magento\Framework\Setup\Declaration\Schema\DataSavior\DumpAccessorInterface" type="Magento\Framework\Setup\Declaration\Schema\FileSystem\Csv" /> + <preference for="Magento\Framework\MessageQueue\ConfigInterface" type="Magento\Framework\MessageQueue\Config\Proxy" /> <preference for="Magento\Framework\MessageQueue\PublisherInterface" type="Magento\Framework\MessageQueue\PublisherPool" /> <preference for="Magento\Framework\MessageQueue\BulkPublisherInterface" type="Magento\Framework\MessageQueue\Bulk\PublisherPool" /> <preference for="Magento\Framework\MessageQueue\MessageIdGeneratorInterface" type="Magento\Framework\MessageQueue\MessageIdGenerator" /> diff --git a/composer.json b/composer.json index 25be12b5bb72f..1af86e438882c 100644 --- a/composer.json +++ b/composer.json @@ -215,6 +215,7 @@ "magento/module-media-content-synchronization-api": "*", "magento/module-media-content-synchronization-catalog": "*", "magento/module-media-content-synchronization-cms": "*", + "magento/module-media-gallery-synchronization-metadata": "*", "magento/module-media-gallery-metadata": "*", "magento/module-media-gallery-metadata-api": "*", "magento/module-media-gallery-catalog-ui": "*", diff --git a/composer.lock b/composer.lock index c2eed9d87cc00..551167152be4d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0b51badfd1978bb34febd90226af9e27", + "content-hash": "aadcf8a265dd7ecbb86dd3dd4e49bc28", "packages": [ { "name": "colinmollenhour/cache-backend-file", @@ -206,6 +206,16 @@ "ssl", "tls" ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], "time": "2020-04-08T08:27:21+00:00" }, { @@ -1346,12 +1356,6 @@ "BSD-3-Clause" ], "description": "Replace zendframework and zfcampus packages with their Laminas Project equivalents.", - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], "time": "2020-05-20T13:45:39+00:00" }, { @@ -3319,12 +3323,6 @@ "laminas", "zf" ], - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], "time": "2020-05-20T16:45:56+00:00" }, { @@ -3564,16 +3562,6 @@ "logging", "psr-3" ], - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", - "type": "tidelift" - } - ], "time": "2020-05-22T07:31:27+00:00" }, { @@ -4366,16 +4354,6 @@ "parser", "validator" ], - "funding": [ - { - "url": "https://github.com/Seldaek", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", - "type": "tidelift" - } - ], "time": "2020-04-30T19:05:18+00:00" }, { @@ -7337,6 +7315,555 @@ ], "time": "2020-06-27T23:57:46+00:00" }, + { + "name": "hoa/consistency", + "version": "1.17.05.02", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Consistency.git", + "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Consistency/zipball/fd7d0adc82410507f332516faf655b6ed22e4c2f", + "reference": "fd7d0adc82410507f332516faf655b6ed22e4c2f", + "shasum": "" + }, + "require": { + "hoa/exception": "~1.0", + "php": ">=5.5.0" + }, + "require-dev": { + "hoa/stream": "~1.0", + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Consistency\\": "." + }, + "files": [ + "Prelude.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Consistency library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "autoloader", + "callable", + "consistency", + "entity", + "flex", + "keyword", + "library" + ], + "time": "2017-05-02T12:18:12+00:00" + }, + { + "name": "hoa/console", + "version": "3.17.05.02", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Console.git", + "reference": "e231fd3ea70e6d773576ae78de0bdc1daf331a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Console/zipball/e231fd3ea70e6d773576ae78de0bdc1daf331a66", + "reference": "e231fd3ea70e6d773576ae78de0bdc1daf331a66", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/file": "~1.0", + "hoa/protocol": "~1.0", + "hoa/stream": "~1.0", + "hoa/ustring": "~4.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "suggest": { + "ext-pcntl": "To enable hoa://Event/Console/Window:resize.", + "hoa/dispatcher": "To use the console kit.", + "hoa/router": "To use the console kit." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Console\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Console library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "autocompletion", + "chrome", + "cli", + "console", + "cursor", + "getoption", + "library", + "option", + "parser", + "processus", + "readline", + "terminfo", + "tput", + "window" + ], + "time": "2017-05-02T12:26:19+00:00" + }, + { + "name": "hoa/event", + "version": "1.17.01.13", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Event.git", + "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Event/zipball/6c0060dced212ffa3af0e34bb46624f990b29c54", + "reference": "6c0060dced212ffa3af0e34bb46624f990b29c54", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Event\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Event library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "event", + "library", + "listener", + "observer" + ], + "time": "2017-01-13T15:30:50+00:00" + }, + { + "name": "hoa/exception", + "version": "1.17.01.16", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Exception.git", + "reference": "091727d46420a3d7468ef0595651488bfc3a458f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Exception/zipball/091727d46420a3d7468ef0595651488bfc3a458f", + "reference": "091727d46420a3d7468ef0595651488bfc3a458f", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Exception\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Exception library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "exception", + "library" + ], + "time": "2017-01-16T07:53:27+00:00" + }, + { + "name": "hoa/file", + "version": "1.17.07.11", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/File.git", + "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/File/zipball/35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", + "reference": "35cb979b779bc54918d2f9a4e02ed6c7a1fa67ca", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/iterator": "~2.0", + "hoa/stream": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\File\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\File library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "Socket", + "directory", + "file", + "finder", + "library", + "link", + "temporary" + ], + "time": "2017-07-11T07:42:15+00:00" + }, + { + "name": "hoa/iterator", + "version": "2.17.01.10", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Iterator.git", + "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Iterator/zipball/d1120ba09cb4ccd049c86d10058ab94af245f0cc", + "reference": "d1120ba09cb4ccd049c86d10058ab94af245f0cc", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Iterator\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Iterator library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "iterator", + "library" + ], + "time": "2017-01-10T10:34:47+00:00" + }, + { + "name": "hoa/protocol", + "version": "1.17.01.14", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Protocol.git", + "reference": "5c2cf972151c45f373230da170ea015deecf19e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Protocol/zipball/5c2cf972151c45f373230da170ea015deecf19e2", + "reference": "5c2cf972151c45f373230da170ea015deecf19e2", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Protocol\\": "." + }, + "files": [ + "Wrapper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Protocol library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "protocol", + "resource", + "stream", + "wrapper" + ], + "time": "2017-01-14T12:26:10+00:00" + }, + { + "name": "hoa/stream", + "version": "1.17.02.21", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Stream.git", + "reference": "3293cfffca2de10525df51436adf88a559151d82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Stream/zipball/3293cfffca2de10525df51436adf88a559151d82", + "reference": "3293cfffca2de10525df51436adf88a559151d82", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/event": "~1.0", + "hoa/exception": "~1.0", + "hoa/protocol": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Stream\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Stream library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "Context", + "bucket", + "composite", + "filter", + "in", + "library", + "out", + "protocol", + "stream", + "wrapper" + ], + "time": "2017-02-21T16:01:06+00:00" + }, + { + "name": "hoa/ustring", + "version": "4.17.01.16", + "source": { + "type": "git", + "url": "https://github.com/hoaproject/Ustring.git", + "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hoaproject/Ustring/zipball/e6326e2739178799b1fe3fdd92029f9517fa17a0", + "reference": "e6326e2739178799b1fe3fdd92029f9517fa17a0", + "shasum": "" + }, + "require": { + "hoa/consistency": "~1.0", + "hoa/exception": "~1.0" + }, + "require-dev": { + "hoa/test": "~2.0" + }, + "suggest": { + "ext-iconv": "ext/iconv must be present (or a third implementation) to use Hoa\\Ustring::transcode().", + "ext-intl": "To get a better Hoa\\Ustring::toAscii() and Hoa\\Ustring::compareTo()." + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Hoa\\Ustring\\": "." + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Ivan Enderlin", + "email": "ivan.enderlin@hoa-project.net" + }, + { + "name": "Hoa community", + "homepage": "https://hoa-project.net/" + } + ], + "description": "The Hoa\\Ustring library.", + "homepage": "https://hoa-project.net/", + "keywords": [ + "library", + "search", + "string", + "unicode" + ], + "time": "2017-01-16T07:08:25+00:00" + }, { "name": "jms/metadata", "version": "1.7.0", @@ -7593,12 +8120,6 @@ "sftp", "storage" ], - "funding": [ - { - "url": "https://offset.earth/frankdejonge", - "type": "other" - } - ], "time": "2020-05-18T15:13:39+00:00" }, { @@ -7709,16 +8230,16 @@ }, { "name": "magento/magento2-functional-testing-framework", - "version": "3.0.0", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/magento/magento2-functional-testing-framework.git", - "reference": "8d98efa7434a30ab9e82ef128c430ef8e3a50699" + "reference": "8a106ea029f222f4354854636861273c7577bee9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/8d98efa7434a30ab9e82ef128c430ef8e3a50699", - "reference": "8d98efa7434a30ab9e82ef128c430ef8e3a50699", + "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/8a106ea029f222f4354854636861273c7577bee9", + "reference": "8a106ea029f222f4354854636861273c7577bee9", "shasum": "" }, "require": { @@ -7736,6 +8257,7 @@ "ext-intl": "*", "ext-json": "*", "ext-openssl": "*", + "hoa/console": "~3.0", "monolog/monolog": "^1.17", "mustache/mustache": "~2.5", "php": "^7.3", @@ -7795,7 +8317,7 @@ "magento", "testing" ], - "time": "2020-07-09T21:26:19+00:00" + "time": "2020-08-19T19:57:27+00:00" }, { "name": "mikey179/vfsstream", @@ -8817,20 +9339,6 @@ "MIT" ], "description": "PHPStan - PHP Static Analysis Tool", - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://www.patreon.com/phpstan", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", - "type": "tidelift" - } - ], "time": "2020-05-05T12:55:44+00:00" }, { @@ -9120,12 +9628,6 @@ "keywords": [ "timer" ], - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], "time": "2020-04-20T06:00:37+00:00" }, { @@ -9181,6 +9683,7 @@ "type": "github" } ], + "abandoned": true, "time": "2020-06-27T06:36:25+00:00" }, { @@ -9269,16 +9772,6 @@ "testing", "xunit" ], - "funding": [ - { - "url": "https://phpunit.de/donate.html", - "type": "custom" - }, - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], "time": "2020-05-22T13:54:05+00:00" }, { @@ -9786,6 +10279,7 @@ ], "description": "FinderFacade is a convenience wrapper for Symfony's Finder component.", "homepage": "https://github.com/sebastianbergmann/finder-facade", + "abandoned": true, "time": "2020-02-08T06:07:58+00:00" }, { diff --git a/dev/tests/acceptance/staticRuleset.json b/dev/tests/acceptance/staticRuleset.json index 74fe3469e353b..82cc9dfe74152 100644 --- a/dev/tests/acceptance/staticRuleset.json +++ b/dev/tests/acceptance/staticRuleset.json @@ -2,6 +2,7 @@ "tests": [ "actionGroupArguments", "deprecatedEntityUsage", - "annotations" + "annotations", + "pauseActionUsage" ] } diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/Item.php b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/Item.php index 1731a974aaed3..71ff93875f2c1 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/Item.php +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/Item.php @@ -11,6 +11,9 @@ use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Query\ResolverInterface; +/** + * Resolver for Item + */ class Item implements ResolverInterface { /** diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/TestUnion.php b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/TestUnion.php new file mode 100644 index 0000000000000..592b0caaa88a3 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/TestUnion.php @@ -0,0 +1,34 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestModuleGraphQlQuery\Model\Resolver; + +use Magento\Framework\GraphQl\Config\Element\Field; +use Magento\Framework\GraphQl\Query\ResolverInterface; +use Magento\Framework\GraphQl\Schema\Type\ResolveInfo; + +/** + * Resolver for Union type TestUnion + */ +class TestUnion implements ResolverInterface +{ + /** + * @inheritDoc + */ + public function resolve( + Field $field, + $context, + ResolveInfo $info, + array $value = null, + array $args = null + ) { + return [ + 'custom_name1' => 'custom_name1_value', + 'custom_name2' => 'custom_name2_value', + ]; + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php new file mode 100644 index 0000000000000..40cbdadb8a948 --- /dev/null +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/Model/Resolver/UnionTypeResolver.php @@ -0,0 +1,27 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestModuleGraphQlQuery\Model\Resolver; + +use Magento\Framework\GraphQl\Query\Resolver\TypeResolverInterface; + +/** + * Type Resolver for union + */ +class UnionTypeResolver implements TypeResolverInterface +{ + /** + * @inheritDoc + */ + public function resolveType(array $data): string + { + if (!empty($data)) { + return 'TypeCustom1'; + } + return ''; + } +} diff --git a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls index 7eb175a88e322..1a5796e07b08b 100644 --- a/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls +++ b/dev/tests/api-functional/_files/Magento/TestModuleGraphQlQuery/etc/schema.graphqls @@ -3,6 +3,7 @@ type Query { testItem(id: Int!) : Item @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\Item") + testUnion: TestUnion @resolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\TestUnion") } type Mutation { @@ -18,3 +19,14 @@ type MutationItem { item_id: Int name: String } + +union TestUnion @doc(description: "some kind of union") @typeResolver(class: "Magento\\TestModuleGraphQlQuery\\Model\\Resolver\\UnionTypeResolver") = + TypeCustom1 | TypeCustom2 + +type TypeCustom1 { + custom_name1: String +} + +type TypeCustom2 { + custom_name2: String +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/Helper/CompareArraysRecursively.php b/dev/tests/api-functional/framework/Magento/TestFramework/Helper/CompareArraysRecursively.php new file mode 100644 index 0000000000000..d8a88c721c9fe --- /dev/null +++ b/dev/tests/api-functional/framework/Magento/TestFramework/Helper/CompareArraysRecursively.php @@ -0,0 +1,70 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Helper; + +/** + * Class for comparing arrays recursively + */ +class CompareArraysRecursively +{ + /** + * Compare arrays recursively regardless of nesting. + * Can compare arrays that have both one level and n-level nesting. + * ``` + * [ + * 'products' => [ + * 'items' => [ + * [ + * 'sku' => 'bundle-product', + * 'type_id' => 'bundle', + * 'items' => [ + * [ + * 'title' => 'Bundle Product Items', + * 'sku' => 'bundle-product', + * 'options' => [ + * [ + * 'price' => 2.75, + * 'label' => 'Simple Product', + * 'product' => [ + * 'name' => 'Simple Product', + * 'sku' => 'simple', + * ] + * ] + * ] + * ] + * ]; + * ``` + * + * @param array $expected + * @param array $actual + * @return array + */ + public function execute(array $expected, array $actual): array + { + $diffResult = []; + + foreach ($expected as $key => $value) { + if (array_key_exists($key, $actual)) { + if (is_array($value)) { + $recursiveDiff = $this->execute($value, $actual[$key]); + if (!empty($recursiveDiff)) { + $diffResult[$key] = $recursiveDiff; + } + } else { + if (!in_array($value, $actual, true)) { + $diffResult[$key] = $value; + } + } + } else { + $diffResult[$key] = $value; + } + } + + return $diffResult; + } +} diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php index 5af6413840c27..2fe93c02e7adb 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQl/Client.php @@ -213,7 +213,7 @@ private function processResponseHeaders(string $headers): array $headerLines = preg_split('/((\r?\n)|(\r\n?))/', $headers); foreach ($headerLines as $headerLine) { - $headerParts = preg_split('/:/', $headerLine); + $headerParts = preg_split('/: /', $headerLine, 2); if (count($headerParts) == 2) { $headersArray[trim($headerParts[0])] = trim($headerParts[1]); } elseif (preg_match('/HTTP\/[\.0-9]+/', $headerLine)) { diff --git a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php index 7f67c8c9ca8df..3de18a932f2cd 100644 --- a/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php +++ b/dev/tests/api-functional/framework/Magento/TestFramework/TestCase/GraphQlAbstract.php @@ -184,61 +184,4 @@ protected function assertResponseFields($actualResponse, $assertionMap) ); } } - - /** - * Compare arrays recursively regardless of nesting. - * - * Can compare arrays that have both one level and n-level nesting. - * ``` - * [ - * 'products' => [ - * 'items' => [ - * [ - * 'sku' => 'bundle-product', - * 'type_id' => 'bundle', - * 'items' => [ - * [ - * 'title' => 'Bundle Product Items', - * 'sku' => 'bundle-product', - * 'options' => [ - * [ - * 'price' => 2.75, - * 'label' => 'Simple Product', - * 'product' => [ - * 'name' => 'Simple Product', - * 'sku' => 'simple', - * ] - * ] - * ] - * ] - * ]; - * ``` - * - * @param array $expected - * @param array $actual - * @return array - */ - public function compareArraysRecursively(array $expected, array $actual): array - { - $diffResult = []; - - foreach ($expected as $key => $value) { - if (array_key_exists($key, $actual)) { - if (is_array($value)) { - $recursiveDiff = $this->compareArraysRecursively($value, $actual[$key]); - if (!empty($recursiveDiff)) { - $diffResult[$key] = $recursiveDiff; - } - } else { - if (!in_array($value, $actual, true)) { - $diffResult[$key] = $value; - } - } - } else { - $diffResult[$key] = $value; - } - } - - return $diffResult; - } } diff --git a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php index bc3869df6a65b..1523bfe957901 100644 --- a/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php +++ b/dev/tests/api-functional/testsuite/Magento/Catalog/Api/CategoryManagementTest.php @@ -9,6 +9,7 @@ use Magento\TestFramework\TestCase\WebapiAbstract; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\CompareArraysRecursively; /** * Tests CategoryManagement @@ -19,6 +20,20 @@ class CategoryManagementTest extends WebapiAbstract const SERVICE_NAME = 'catalogCategoryManagementV1'; + /** + * @var CompareArraysRecursively + */ + private $compareArraysRecursively; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + $this->compareArraysRecursively = $objectManager->create(CompareArraysRecursively::class); + } + /** * Tests getTree operation * @@ -40,8 +55,8 @@ public function testTree($rootCategoryId, $depth, $expected) ] ]; $result = $this->_webApiCall($serviceInfo, $requestData); - $expected = array_replace_recursive($result, $expected); - $this->assertEquals($expected, $result); + $diff = $this->compareArraysRecursively->execute($expected, $result); + self::assertEquals([], $diff, "Actual categories response doesn't equal expected data"); } /** diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductMultipleOptionsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductMultipleOptionsTest.php index 3409b5e3af1af..77c4d5b84e72e 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductMultipleOptionsTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Bundle/BundleProductMultipleOptionsTest.php @@ -7,6 +7,8 @@ namespace Magento\GraphQl\Bundle; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\CompareArraysRecursively; use Magento\TestFramework\TestCase\GraphQlAbstract; /** @@ -14,6 +16,20 @@ */ class BundleProductMultipleOptionsTest extends GraphQlAbstract { + /** + * @var CompareArraysRecursively + */ + private $compareArraysRecursively; + + /** + * @inheritDoc + */ + protected function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + $this->compareArraysRecursively = $objectManager->create(CompareArraysRecursively::class); + } + /** * @magentoApiDataFixture Magento/Bundle/_files/product_with_multiple_options.php * @param array $bundleProductDataProvider @@ -85,7 +101,7 @@ private function assertBundleProduct(array $response, array $bundleProductDataPr $productItems = $response['products']['items']; foreach ($bundleProductDataProvider as $key => $data) { - $diff = $this->compareArraysRecursively($data, $productItems[$key]); + $diff = $this->compareArraysRecursively->execute($data, $productItems[$key]); self::assertEquals([], $diff, "Actual response doesn't equal to expected data"); } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CorsHeadersTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CorsHeadersTest.php index 25c808a549e80..b8f59b34fae0c 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/CorsHeadersTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CorsHeadersTest.php @@ -76,7 +76,7 @@ public function testCorsHeadersWhenCorsIsEnabled(): void $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOWED_HEADERS, 'Origin'); $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOW_CREDENTIALS, '1'); $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOWED_METHODS, 'GET,POST'); - $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOWED_ORIGINS, 'magento.local'); + $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_ALLOWED_ORIGINS, 'http://magento.local'); $this->resourceConfig->saveConfig(Configuration::XML_PATH_CORS_MAX_AGE, '86400'); $this->reinitConfig->reinit(); @@ -85,7 +85,7 @@ public function testCorsHeadersWhenCorsIsEnabled(): void self::assertEquals('Origin', $headers['Access-Control-Allow-Headers']); self::assertEquals('1', $headers['Access-Control-Allow-Credentials']); self::assertEquals('GET,POST', $headers['Access-Control-Allow-Methods']); - self::assertEquals('magento.local', $headers['Access-Control-Allow-Origin']); + self::assertEquals('http://magento.local', $headers['Access-Control-Allow-Origin']); self::assertEquals('86400', $headers['Access-Control-Max-Age']); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php index 2db06e383758f..0bbdf5a4c9803 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/TestModule/GraphQlQueryTest.php @@ -10,7 +10,7 @@ use Magento\TestFramework\TestCase\GraphQlAbstract; /** - * Class GraphQlQueryTest + * Test for basic GraphQl features */ class GraphQlQueryTest extends GraphQlAbstract { @@ -100,4 +100,29 @@ public function testQueryViaGetRequestWithVariablesReturnsResults() $this->assertArrayHasKey('testItem', $response); } + + public function testQueryTestUnionResults() + { + $query = <<<QUERY +{ + testUnion { + __typename + ... on TypeCustom1 { + custom_name1 + } + ... on TypeCustom2 { + custom_name2 + } + } +} +QUERY; + + $response = $this->graphQlQuery($query); + + $this->assertArrayHasKey('testUnion', $response); + $testUnion = $response['testUnion']; + $this->assertArrayHasKey('custom_name1', $testUnion); + $this->assertEquals('custom_name1_value', $testUnion['custom_name1']); + $this->assertArrayNotHasKey('custom_name2', $testUnion); + } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddBundleProductToWishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddBundleProductToWishlistTest.php index a81ec701b22a8..b97cd379e4384 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddBundleProductToWishlistTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddBundleProductToWishlistTest.php @@ -16,6 +16,7 @@ use Magento\Integration\Api\CustomerTokenServiceInterface; use Magento\TestFramework\Helper\Bootstrap; use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Ui\Component\Form\Element\Select; use Magento\Wishlist\Model\Item; use Magento\Wishlist\Model\WishlistFactory; @@ -74,7 +75,7 @@ public function testAddBundleProductWithOptions(): void $selection = $typeInstance->getSelectionsCollection([$option->getId()], $product)->getFirstItem(); $optionId = $option->getId(); $selectionId = $selection->getSelectionId(); - $bundleOptions = $this->generateBundleOptionIdV2((int) $optionId, (int) $selectionId, $optionQty); + $bundleOptions = $this->generateBundleOptionUid((int) $optionId, (int) $selectionId, $optionQty); $query = $this->getQuery($sku, $qty, $bundleOptions); $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); @@ -88,9 +89,13 @@ public function testAddBundleProductWithOptions(): void $this->assertEquals($wishlist->getItemsCount(), $response['items_count']); $this->assertEquals($wishlist->getSharingCode(), $response['sharing_code']); $this->assertEquals($wishlist->getUpdatedAt(), $response['updated_at']); - $this->assertEquals($item->getData('qty'), $response['items'][0]['qty']); - $this->assertEquals($item->getDescription(), $response['items'][0]['description']); - $this->assertEquals($item->getAddedAt(), $response['items'][0]['added_at']); + $this->assertEquals($item->getData('qty'), $response['items_v2'][0]['quantity']); + $this->assertEquals($item->getDescription(), $response['items_v2'][0]['description']); + $this->assertEquals($item->getAddedAt(), $response['items_v2'][0]['added_at']); + $this->assertNotEmpty($response['items_v2'][0]['bundle_options']); + $bundleOptions = $response['items_v2'][0]['bundle_options']; + $this->assertEquals('Bundle Product Items', $bundleOptions[0]['label']); + $this->assertEquals(Select::NAME, $bundleOptions[0]['type']); } /** @@ -149,11 +154,24 @@ private function getQuery( sharing_code items_count updated_at - items { + items_v2 { id description - qty + quantity added_at + ... on BundleWishlistItem { + bundle_options { + id + label + type + values { + id + label + quantity + price + } + } + } } } } @@ -169,7 +187,7 @@ private function getQuery( * * @return string */ - private function generateBundleOptionIdV2(int $optionId, int $selectionId, int $quantity): string + private function generateBundleOptionUid(int $optionId, int $selectionId, int $quantity): string { return base64_encode("bundle/$optionId/$selectionId/$quantity"); } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddConfigurableProductToWishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddConfigurableProductToWishlistTest.php index d8d44541f899d..cffc5eb6f93c1 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddConfigurableProductToWishlistTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddConfigurableProductToWishlistTest.php @@ -48,7 +48,7 @@ protected function setUp(): void * * @throws Exception */ - public function testAddDownloadableProductWithOptions(): void + public function testAddConfigurableProductWithOptions(): void { $product = $this->getConfigurableProductInfo(); $customerId = 1; @@ -57,7 +57,7 @@ public function testAddDownloadableProductWithOptions(): void $valueIndex = $product['configurable_options'][0]['values'][0]['value_index']; $childSku = $product['variants'][0]['product']['sku']; $parentSku = $product['sku']; - $selectedConfigurableOptionsQuery = $this->generateSuperAttributesIdV2Query($attributeId, $valueIndex); + $selectedConfigurableOptionsQuery = $this->generateSuperAttributesUidQuery($attributeId, $valueIndex); $query = $this->getQuery($parentSku, $childSku, $qty, $selectedConfigurableOptionsQuery); @@ -66,16 +66,19 @@ public function testAddDownloadableProductWithOptions(): void /** @var Item $wishlistItem */ $wishlistItem = $wishlist->getItemCollection()->getFirstItem(); - self::assertArrayHasKey('addProductsToWishlist', $response); - self::assertArrayHasKey('wishlist', $response['addProductsToWishlist']); + $this->assertArrayHasKey('addProductsToWishlist', $response); + $this->assertArrayHasKey('wishlist', $response['addProductsToWishlist']); $wishlistResponse = $response['addProductsToWishlist']['wishlist']; - self::assertEquals($wishlist->getItemsCount(), $wishlistResponse['items_count']); - self::assertEquals($wishlist->getSharingCode(), $wishlistResponse['sharing_code']); - self::assertEquals($wishlist->getUpdatedAt(), $wishlistResponse['updated_at']); - self::assertEquals($wishlistItem->getId(), $wishlistResponse['items'][0]['id']); - self::assertEquals($wishlistItem->getData('qty'), $wishlistResponse['items'][0]['qty']); - self::assertEquals($wishlistItem->getDescription(), $wishlistResponse['items'][0]['description']); - self::assertEquals($wishlistItem->getAddedAt(), $wishlistResponse['items'][0]['added_at']); + $this->assertEquals($wishlist->getItemsCount(), $wishlistResponse['items_count']); + $this->assertEquals($wishlist->getSharingCode(), $wishlistResponse['sharing_code']); + $this->assertEquals($wishlist->getUpdatedAt(), $wishlistResponse['updated_at']); + $this->assertEquals($wishlistItem->getId(), $wishlistResponse['items_v2'][0]['id']); + $this->assertEquals($wishlistItem->getData('qty'), $wishlistResponse['items_v2'][0]['quantity']); + $this->assertEquals($wishlistItem->getDescription(), $wishlistResponse['items_v2'][0]['description']); + $this->assertEquals($wishlistItem->getAddedAt(), $wishlistResponse['items_v2'][0]['added_at']); + $this->assertNotEmpty($wishlistResponse['items_v2'][0]['configurable_options']); + $configurableOptions = $wishlistResponse['items_v2'][0]['configurable_options']; + $this->assertEquals('Test Configurable', $configurableOptions[0]['option_label']); } /** @@ -135,11 +138,20 @@ private function getQuery( sharing_code items_count updated_at - items { + items_v2 { id description - qty + quantity added_at + ... on ConfigurableWishlistItem { + child_sku + configurable_options { + id + option_label + value_id + value_label + } + } } } } @@ -148,14 +160,14 @@ private function getQuery( } /** - * Generates Id_v2 for super configurable product super attributes + * Generates uid for super configurable product super attributes * * @param int $attributeId * @param int $valueIndex * * @return string */ - private function generateSuperAttributesIdV2Query(int $attributeId, int $valueIndex): string + private function generateSuperAttributesUidQuery(int $attributeId, int $valueIndex): string { return 'selected_options: ["' . base64_encode("configurable/$attributeId/$valueIndex") . '"]'; } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddDownloadableProductToWishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddDownloadableProductToWishlistTest.php index 489a960056f1b..0de45fb21b20b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddDownloadableProductToWishlistTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/AddDownloadableProductToWishlistTest.php @@ -37,9 +37,9 @@ class AddDownloadableProductToWishlistTest extends GraphQlAbstract private $wishlistFactory; /** - * @var GetCustomOptionsWithIDV2ForQueryBySku + * @var GetCustomOptionsWithUidForQueryBySku */ - private $getCustomOptionsWithIDV2ForQueryBySku; + private $getCustomOptionsWithUidForQueryBySku; /** * Set Up @@ -49,69 +49,75 @@ protected function setUp(): void $this->objectManager = Bootstrap::getObjectManager(); $this->customerTokenService = $this->objectManager->get(CustomerTokenServiceInterface::class); $this->wishlistFactory = $this->objectManager->get(WishlistFactory::class); - $this->getCustomOptionsWithIDV2ForQueryBySku = - $this->objectManager->get(GetCustomOptionsWithIDV2ForQueryBySku::class); + $this->getCustomOptionsWithUidForQueryBySku = + $this->objectManager->get(GetCustomOptionsWithUidForQueryBySku::class); } /** - * @magentoConfigFixture default_store wishlist/general/active 1 + * @magentoConfigFixture default_store wishlist/general/active 0 * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_custom_options.php */ - public function testAddDownloadableProductWithOptions(): void + public function testAddDownloadableProductOnDisabledWishlist(): void { - $customerId = 1; - $sku = 'downloadable-product-with-purchased-separately-links'; $qty = 2; + $sku = 'downloadable-product-with-purchased-separately-links'; $links = $this->getProductsLinks($sku); $linkId = key($links); - $itemOptions = $this->getCustomOptionsWithIDV2ForQueryBySku->execute($sku); + $itemOptions = $this->getCustomOptionsWithUidForQueryBySku->execute($sku); $itemOptions['selected_options'][] = $this->generateProductLinkSelectedOptions($linkId); - $productOptionsQuery = preg_replace( + $productOptionsQuery = trim(preg_replace( '/"([^"]+)"\s*:\s*/', '$1:', json_encode($itemOptions) - ); - $query = $this->getQuery($qty, $sku, trim($productOptionsQuery, '{}')); - $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); - $wishlist = $this->wishlistFactory->create(); - $wishlist->loadByCustomerId($customerId, true); - /** @var Item $wishlistItem */ - $wishlistItem = $wishlist->getItemCollection()->getFirstItem(); - - self::assertArrayHasKey('addProductsToWishlist', $response); - self::assertArrayHasKey('wishlist', $response['addProductsToWishlist']); - $wishlistResponse = $response['addProductsToWishlist']['wishlist']; - self::assertEquals($wishlist->getItemsCount(), $wishlistResponse['items_count']); - self::assertEquals($wishlist->getSharingCode(), $wishlistResponse['sharing_code']); - self::assertEquals($wishlist->getUpdatedAt(), $wishlistResponse['updated_at']); - self::assertEquals($wishlistItem->getId(), $wishlistResponse['items'][0]['id']); - self::assertEquals($wishlistItem->getData('qty'), $wishlistResponse['items'][0]['qty']); - self::assertEquals($wishlistItem->getDescription(), $wishlistResponse['items'][0]['description']); - self::assertEquals($wishlistItem->getAddedAt(), $wishlistResponse['items'][0]['added_at']); + ), '{}'); + $query = $this->getQuery($qty, $sku, $productOptionsQuery); + $this->expectExceptionMessage('The wishlist configuration is currently disabled.'); + $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** - * @magentoConfigFixture default_store wishlist/general/active 0 + * @magentoConfigFixture default_store wishlist/general/active 1 * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable_with_custom_options.php */ - public function testAddDownloadableProductOnDisabledWishlist(): void + public function testAddDownloadableProductWithOptions(): void { - $qty = 2; + $customerId = 1; $sku = 'downloadable-product-with-purchased-separately-links'; + $qty = 2; $links = $this->getProductsLinks($sku); $linkId = key($links); - $itemOptions = $this->getCustomOptionsWithIDV2ForQueryBySku->execute($sku); + $itemOptions = $this->getCustomOptionsWithUidForQueryBySku->execute($sku); $itemOptions['selected_options'][] = $this->generateProductLinkSelectedOptions($linkId); - $productOptionsQuery = trim(preg_replace( + $productOptionsQuery = preg_replace( '/"([^"]+)"\s*:\s*/', '$1:', json_encode($itemOptions) - ), '{}'); - $query = $this->getQuery($qty, $sku, $productOptionsQuery); - $this->expectExceptionMessage('The wishlist is not currently available.'); - $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + ); + $query = $this->getQuery($qty, $sku, trim($productOptionsQuery, '{}')); + $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); + $wishlist = $this->wishlistFactory->create(); + $wishlist->loadByCustomerId($customerId, true); + /** @var Item $wishlistItem */ + $wishlistItem = $wishlist->getItemCollection()->getFirstItem(); + + $this->assertArrayHasKey('addProductsToWishlist', $response); + $this->assertArrayHasKey('wishlist', $response['addProductsToWishlist']); + $wishlistResponse = $response['addProductsToWishlist']['wishlist']; + $this->assertEquals($wishlist->getItemsCount(), $wishlistResponse['items_count']); + $this->assertEquals($wishlist->getSharingCode(), $wishlistResponse['sharing_code']); + $this->assertEquals($wishlist->getUpdatedAt(), $wishlistResponse['updated_at']); + $this->assertEquals($wishlistItem->getId(), $wishlistResponse['items_v2'][0]['id']); + $this->assertEquals($wishlistItem->getData('qty'), $wishlistResponse['items_v2'][0]['quantity']); + $this->assertEquals($wishlistItem->getDescription(), $wishlistResponse['items_v2'][0]['description']); + $this->assertEquals($wishlistItem->getAddedAt(), $wishlistResponse['items_v2'][0]['added_at']); + $this->assertNotEmpty($wishlistResponse['items_v2'][0]['links_v2']); + $wishlistItemLinks = $wishlistResponse['items_v2'][0]['links_v2']; + $this->assertEquals('Downloadable Product Link 1', $wishlistItemLinks[0]['title']); + $this->assertNotEmpty($wishlistResponse['items_v2'][0]['samples']); + $wishlistItemSamples = $wishlistResponse['items_v2'][0]['samples']; + $this->assertEquals('Downloadable Product Sample', $wishlistItemSamples[0]['title']); } /** @@ -190,11 +196,23 @@ private function getQuery( sharing_code items_count updated_at - items { + items_v2 { id description - qty + quantity added_at + ... on DownloadableWishlistItem { + links_v2 { + id + title + sample_url + } + samples { + id + title + sample_url + } + } } } } @@ -203,7 +221,7 @@ private function getQuery( } /** - * Generates Id_v2 for downloadable links + * Generates uid for downloadable links * * @param int $linkId * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/CustomerWishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/CustomerWishlistTest.php index 0a8e1757a2ce2..04095c1679d2f 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/CustomerWishlistTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/CustomerWishlistTest.php @@ -131,7 +131,7 @@ public function testGuestCannotGetWishlist() public function testCustomerCannotGetWishlistWhenDisabled() { $this->expectException(\Exception::class); - $this->expectExceptionMessage('The wishlist is not currently available.'); + $this->expectExceptionMessage('The wishlist configuration is currently disabled.'); $query = <<<QUERY diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/CustomerWishlistsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/CustomerWishlistsTest.php new file mode 100644 index 0000000000000..e452e70c24148 --- /dev/null +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/CustomerWishlistsTest.php @@ -0,0 +1,142 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\GraphQl\Wishlist; + +use Exception; +use Magento\Framework\Exception\AuthenticationException; +use Magento\Integration\Api\CustomerTokenServiceInterface; +use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\TestCase\GraphQlAbstract; +use Magento\Wishlist\Model\Item; +use Magento\Wishlist\Model\ResourceModel\Wishlist\CollectionFactory; +use Magento\Wishlist\Model\Wishlist; + +/** + * Test coverage for customer wishlists + */ +class CustomerWishlistsTest extends GraphQlAbstract +{ + /** + * @var CustomerTokenServiceInterface + */ + private $customerTokenService; + + /** + * @var CollectionFactory + */ + private $wishlistCollectionFactory; + + /** + * Set Up + */ + protected function setUp(): void + { + $this->customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class); + $this->wishlistCollectionFactory = Bootstrap::getObjectManager()->get(CollectionFactory::class); + } + + /** + * Test fetching customer wishlist + * + * @magentoConfigFixture default_store wishlist/general/active 1 + * @magentoApiDataFixture Magento/Wishlist/_files/wishlist.php + */ + public function testCustomerWishlist(): void + { + $customerId = 1; + /** @var Wishlist $wishlist */ + $collection = $this->wishlistCollectionFactory->create()->filterByCustomerId($customerId); + /** @var Item $wishlistItem */ + $wishlistItem = $collection->getFirstItem(); + $response = $this->graphQlQuery( + $this->getQuery(), + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', 'password') + ); + $this->assertArrayHasKey('wishlists', $response['customer']); + $wishlist = $response['customer']['wishlists'][0]; + $this->assertEquals($wishlistItem->getItemsCount(), $wishlist['items_count']); + $this->assertEquals($wishlistItem->getSharingCode(), $wishlist['sharing_code']); + $this->assertEquals($wishlistItem->getUpdatedAt(), $wishlist['updated_at']); + $wishlistItemResponse = $wishlist['items_v2'][0]; + $this->assertEquals('simple', $wishlistItemResponse['product']['sku']); + } + + /** + * Testing fetching the wishlist when wishlist is disabled + * + * @magentoConfigFixture default_store wishlist/general/active 0 + * @magentoApiDataFixture Magento/Customer/_files/customer.php + */ + public function testCustomerCannotGetWishlistWhenDisabled(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('The wishlist configuration is currently disabled.'); + $this->graphQlQuery( + $this->getQuery(), + [], + '', + $this->getCustomerAuthHeaders('customer@example.com', 'password') + ); + } + + /** + * Test wishlist fetching for a guest customer + * + * @magentoConfigFixture default_store wishlist/general/active 1 + */ + public function testGuestCannotGetWishlist(): void + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('The current customer isn\'t authorized.'); + $this->graphQlQuery($this->getQuery()); + } + + /** + * Returns GraphQl query string + * + * @return string + */ + private function getQuery(): string + { + return <<<QUERY +query { + customer { + wishlists { + items_count + sharing_code + updated_at + items_v2 { + product { + sku + } + } + } + } +} +QUERY; + } + + /** + * Getting customer auth headers + * + * @param string $email + * @param string $password + * + * @return array + * + * @throws AuthenticationException + */ + private function getCustomerAuthHeaders(string $email, string $password): array + { + $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password); + + return ['Authorization' => 'Bearer ' . $customerToken]; + } +} diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/DeleteProductsFromWishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/DeleteProductsFromWishlistTest.php index ebe99289b8934..13aaecbc7b733 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/DeleteProductsFromWishlistTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/DeleteProductsFromWishlistTest.php @@ -42,17 +42,17 @@ public function testDeleteWishlistItemFromWishlist(): void $wishlist = $this->getWishlist(); $wishlistId = $wishlist['customer']['wishlist']['id']; $wishlist = $wishlist['customer']['wishlist']; - $wishlistItems = $wishlist['items']; - self::assertEquals(1, $wishlist['items_count']); + $wishlistItems = $wishlist['items_v2']; + $this->assertEquals(1, $wishlist['items_count']); $query = $this->getQuery((int) $wishlistId, (int) $wishlistItems[0]['id']); $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); - self::assertArrayHasKey('removeProductsFromWishlist', $response); - self::assertArrayHasKey('wishlist', $response['removeProductsFromWishlist']); + $this->assertArrayHasKey('removeProductsFromWishlist', $response); + $this->assertArrayHasKey('wishlist', $response['removeProductsFromWishlist']); $wishlistResponse = $response['removeProductsFromWishlist']['wishlist']; - self::assertEquals(0, $wishlistResponse['items_count']); - self::assertEmpty($wishlistResponse['items']); + $this->assertEquals(0, $wishlistResponse['items_count']); + $this->assertEmpty($wishlistResponse['items_v2']); } /** @@ -98,10 +98,10 @@ private function getQuery( id sharing_code items_count - items { + items_v2 { id description - qty + quantity } } } @@ -134,9 +134,9 @@ private function getCustomerWishlistQuery(): string wishlist { id items_count - items { + items_v2 { id - qty + quantity description } } diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/GetCustomOptionsWithIDV2ForQueryBySku.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/GetCustomOptionsWithUidForQueryBySku.php similarity index 94% rename from dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/GetCustomOptionsWithIDV2ForQueryBySku.php rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/GetCustomOptionsWithUidForQueryBySku.php index fcba7458f317a..4bd0c135f039a 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/GetCustomOptionsWithIDV2ForQueryBySku.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/GetCustomOptionsWithUidForQueryBySku.php @@ -10,9 +10,9 @@ use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface; /** - * Generate an array with test values for customizable options with encoded id_v2 value + * Generate an array with test values for customizable options with encoded uid value */ -class GetCustomOptionsWithIDV2ForQueryBySku +class GetCustomOptionsWithUidForQueryBySku { /** * @var ProductCustomOptionRepositoryInterface @@ -71,7 +71,7 @@ public function execute(string $sku): array } /** - * Returns id_v2 of the selected custom option + * Returns uid of the selected custom option * * @param int $optionId * @param int $optionValueId @@ -84,7 +84,7 @@ private function encodeSelectedOption(int $optionId, int $optionValueId): string } /** - * Returns id_v2 of the entered custom option + * Returns uid of the entered custom option * * @param int $optionId * diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/UpdateProductsFromWishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/UpdateProductsFromWishlistTest.php index 9a9cd424e54ca..08273e7936640 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/UpdateProductsFromWishlistTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/UpdateProductsFromWishlistTest.php @@ -43,18 +43,18 @@ public function testUpdateSimpleProductFromWishlist(): void $qty = 5; $description = 'New Description'; $wishlistId = $wishlist['customer']['wishlist']['id']; - $wishlistItem = $wishlist['customer']['wishlist']['items'][0]; - self::assertNotEquals($description, $wishlistItem['description']); - self::assertNotEquals($qty, $wishlistItem['qty']); + $wishlistItem = $wishlist['customer']['wishlist']['items_v2'][0]; + $this->assertNotEquals($description, $wishlistItem['description']); + $this->assertNotEquals($qty, $wishlistItem['quantity']); $query = $this->getQuery((int) $wishlistId, (int) $wishlistItem['id'], $qty, $description); $response = $this->graphQlMutation($query, [], '', $this->getHeaderMap()); - self::assertArrayHasKey('updateProductsInWishlist', $response); - self::assertArrayHasKey('wishlist', $response['updateProductsInWishlist']); + $this->assertArrayHasKey('updateProductsInWishlist', $response); + $this->assertArrayHasKey('wishlist', $response['updateProductsInWishlist']); $wishlistResponse = $response['updateProductsInWishlist']['wishlist']; - self::assertEquals($qty, $wishlistResponse['items'][0]['qty']); - self::assertEquals($description, $wishlistResponse['items'][0]['description']); + $this->assertEquals($qty, $wishlistResponse['items_v2'][0]['quantity']); + $this->assertEquals($description, $wishlistResponse['items_v2'][0]['description']); } /** @@ -110,10 +110,10 @@ private function getQuery( id sharing_code items_count - items { + items_v2 { id description - qty + quantity } } } @@ -146,9 +146,9 @@ private function getCustomerWishlistQuery(): string wishlist { id items_count - items { + items_v2 { id - qty + quantity description } } diff --git a/dev/tests/integration/etc/install-config-mysql.php.dist b/dev/tests/integration/etc/install-config-mysql.php.dist index 4766048c62375..1d4b3d1951e32 100644 --- a/dev/tests/integration/etc/install-config-mysql.php.dist +++ b/dev/tests/integration/etc/install-config-mysql.php.dist @@ -11,6 +11,9 @@ return [ 'db-name' => 'magento_integration_tests', 'db-prefix' => '', 'backend-frontname' => 'backend', + 'search-engine' => 'elasticsearch7', + 'elasticsearch-host' => 'localhost', + 'elasticsearch-port' => 9200, 'admin-user' => \Magento\TestFramework\Bootstrap::ADMIN_NAME, 'admin-password' => \Magento\TestFramework\Bootstrap::ADMIN_PASSWORD, 'admin-email' => \Magento\TestFramework\Bootstrap::ADMIN_EMAIL, diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php index 0d2f9d63c5d7f..8c25a82e0f6fd 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/CategoryTest.php @@ -15,6 +15,8 @@ use Magento\Catalog\Model\ResourceModel\Category\Tree; use Magento\Catalog\Model\ResourceModel\Product\Collection as ProductCollection; use Magento\Eav\Model\Entity\Attribute\Exception as AttributeException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Math\Random; use Magento\Framework\Url; use Magento\Store\Api\StoreRepositoryInterface; use Magento\Store\Model\Store; @@ -419,6 +421,29 @@ public function testCategoryCreateWithDifferentFields(array $data): void $this->assertSame($data, $categoryData); } + /** + * Test for Category Description field to be able to contain >64kb of data + * + * @throws NoSuchEntityException + * @throws \Exception + */ + public function testMaximumDescriptionLength(): void + { + $random = Bootstrap::getObjectManager()->get(Random::class); + $longDescription = $random->getRandomString(70000); + + $requiredData = [ + 'name' => 'Test Category', + 'attribute_set_id' => '3', + 'parent_id' => 2, + 'description' => $longDescription + ]; + $this->_model->setData($requiredData); + $this->categoryResource->save($this->_model); + $category = $this->categoryRepository->get($this->_model->getId()); + $this->assertEquals($longDescription, $category->getDescription()); + } + /** * @return array */ diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php index b56e9e502cce6..b0f36f250991b 100644 --- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php +++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ProductTest.php @@ -8,14 +8,19 @@ namespace Magento\Catalog\Model; -use Magento\Eav\Model\Config as EavConfig; -use Magento\Catalog\Model\Product; -use Magento\Framework\App\Filesystem\DirectoryList; -use Magento\TestFramework\ObjectManager; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\Product\Attribute\Source\Status; +use Magento\Catalog\Model\Product\Visibility; +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\CouldNotSaveException; +use Magento\Framework\Exception\InputException; +use Magento\Framework\Exception\LocalizedException; +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\Exception\StateException; +use Magento\Framework\Math\Random; use Magento\Framework\ObjectManagerInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\ObjectManager; /** * Tests product model: @@ -119,14 +124,62 @@ public function testCRUD() )->setMetaDescription( 'meta description' )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + Visibility::VISIBILITY_BOTH )->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + Status::STATUS_ENABLED ); $crud = new \Magento\TestFramework\Entity($this->_model, ['sku' => uniqid()]); $crud->testCrud(); } + /** + * Test for Product Description field to be able to contain >64kb of data + * + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + * @magentoAppArea adminhtml + * @throws NoSuchEntityException + * @throws CouldNotSaveException + * @throws InputException + * @throws StateException + * @throws LocalizedException + */ + public function testMaximumDescriptionLength() + { + $sku = uniqid(); + $random = Bootstrap::getObjectManager()->get(Random::class); + $longDescription = $random->getRandomString(70000); + + $this->_model->setTypeId( + 'simple' + )->setAttributeSetId( + 4 + )->setName( + 'Simple Product With Long Description' + )->setDescription( + $longDescription + )->setSku( + $sku + )->setPrice( + 10 + )->setMetaTitle( + 'meta title' + )->setMetaKeyword( + 'meta keyword' + )->setMetaDescription( + 'meta description' + )->setVisibility( + Visibility::VISIBILITY_BOTH + )->setStatus( + Status::STATUS_ENABLED + ); + + $this->productRepository->save($this->_model); + $product = $this->productRepository->get($sku); + + $this->assertEquals($longDescription, $product->getDescription()); + } + /** * Test clean cache * @@ -219,7 +272,7 @@ public function testDuplicate() $this->assertNotEquals($duplicate->getId(), $this->_model->getId()); $this->assertNotEquals($duplicate->getSku(), $this->_model->getSku()); $this->assertEquals( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED, + Status::STATUS_DISABLED, $duplicate->getStatus() ); $this->assertEquals(\Magento\Store\Model\Store::DEFAULT_STORE_ID, $duplicate->getStoreId()); @@ -275,35 +328,35 @@ protected function _undo($duplicate) public function testVisibilityApi() { $this->assertEquals( - [\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED], + [Status::STATUS_ENABLED], $this->_model->getVisibleInCatalogStatuses() ); $this->assertEquals( - [\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED], + [Status::STATUS_ENABLED], $this->_model->getVisibleStatuses() ); - $this->_model->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED); + $this->_model->setStatus(Status::STATUS_DISABLED); $this->assertFalse($this->_model->isVisibleInCatalog()); - $this->_model->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED); + $this->_model->setStatus(Status::STATUS_ENABLED); $this->assertTrue($this->_model->isVisibleInCatalog()); $this->assertEquals( [ - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_SEARCH, - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG, - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH, + Visibility::VISIBILITY_IN_SEARCH, + Visibility::VISIBILITY_IN_CATALOG, + Visibility::VISIBILITY_BOTH, ], $this->_model->getVisibleInSiteVisibilities() ); $this->assertFalse($this->_model->isVisibleInSiteVisibility()); - $this->_model->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_SEARCH); + $this->_model->setVisibility(Visibility::VISIBILITY_IN_SEARCH); $this->assertTrue($this->_model->isVisibleInSiteVisibility()); - $this->_model->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG); + $this->_model->setVisibility(Visibility::VISIBILITY_IN_CATALOG); $this->assertTrue($this->_model->isVisibleInSiteVisibility()); - $this->_model->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH); + $this->_model->setVisibility(Visibility::VISIBILITY_BOTH); $this->assertTrue($this->_model->isVisibleInSiteVisibility()); } @@ -509,9 +562,9 @@ public function testValidate() )->setMetaDescription( 'meta description' )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + Visibility::VISIBILITY_BOTH )->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + Status::STATUS_ENABLED )->setCollectExceptionMessages( true ); @@ -551,9 +604,9 @@ public function testValidateUniqueInputAttributeValue() $attribute->getAttributeCode(), 'unique value' )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + Visibility::VISIBILITY_BOTH )->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + Status::STATUS_ENABLED )->setCollectExceptionMessages( true ); @@ -600,9 +653,9 @@ public function testValidateUniqueInputAttributeOnTheSameProduct() $attribute->getAttributeCode(), 'unique value' )->setVisibility( - \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH + Visibility::VISIBILITY_BOTH )->setStatus( - \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED + Status::STATUS_ENABLED )->setCollectExceptionMessages( true ); @@ -675,10 +728,10 @@ public function testSaveWithBackordersEnabled(int $qty, int $stockStatus, bool $ * @magentoDataFixture Magento/Catalog/_files/product_simple.php * * @return void - * @throws \Magento\Framework\Exception\CouldNotSaveException - * @throws \Magento\Framework\Exception\InputException - * @throws \Magento\Framework\Exception\NoSuchEntityException - * @throws \Magento\Framework\Exception\StateException + * @throws CouldNotSaveException + * @throws InputException + * @throws NoSuchEntityException + * @throws StateException */ public function testProductStatusWhenCatalogFlatProductIsEnabled() { diff --git a/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContentTest.php b/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContentTest.php new file mode 100644 index 0000000000000..076a669f3f8ad --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Cms/Model/Wysiwyg/Images/GetInsertImageContentTest.php @@ -0,0 +1,133 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ + +declare(strict_types=1); + +namespace Magento\Cms\Model\Wysiwyg\Images; + +use Magento\Backend\Model\UrlInterface; +use Magento\Cms\Helper\Wysiwyg\Images as ImagesHelper; +use Magento\Framework\Url\EncoderInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +class GetInsertImageContentTest extends TestCase +{ + /** + * @var GetInsertImageContent + */ + private $getInsertImageContent; + + /** + * @var ImagesHelper + */ + private $imagesHelper; + + /** + * @var EncoderInterface + */ + private $urlEncoder; + + /** + * @var UrlInterface + */ + protected $url; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->getInsertImageContent = Bootstrap::getObjectManager()->get(GetInsertImageContent::class); + $this->imagesHelper = Bootstrap::getObjectManager()->get(ImagesHelper::class); + $this->urlEncoder = Bootstrap::getObjectManager()->get(EncoderInterface::class); + $this->url = Bootstrap::getObjectManager()->get(UrlInterface::class); + } + + /** + * Test for GetInsertImageContent::execute + * + * @dataProvider imageDataProvider + * @param string $filename + * @param bool $forceStaticPath + * @param bool $renderAsTag + * @param int|null $storeId + * @param string $expectedResult + */ + public function testExecute( + string $filename, + bool $forceStaticPath, + bool $renderAsTag, + ?int $storeId, + string $expectedResult + ): void { + if (!$forceStaticPath && !$renderAsTag && !$this->imagesHelper->isUsingStaticUrlsAllowed()) { + $expectedResult = $this->url->getUrl( + 'cms/wysiwyg/directive', + [ + '___directive' => $this->urlEncoder->encode($expectedResult), + '_escape_params' => false + ] + ); + } + + $this->assertEquals( + $expectedResult, + $this->getInsertImageContent->execute( + $this->imagesHelper->idEncode($filename), + $forceStaticPath, + $renderAsTag, + $storeId + ) + ); + } + + /** + * Data provider for testExecute + * + * @return array[] + */ + public function imageDataProvider(): array + { + return [ + [ + 'test-image.jpg', + false, + true, + 1, + '<img src="{{media url="test-image.jpg"}}" alt="" />' + ], + [ + 'catalog/category/test-image.jpg', + true, + false, + 1, + '/pub/media/catalog/category/test-image.jpg' + ], + [ + 'test-image.jpg', + false, + false, + 1, + '{{media url="test-image.jpg"}}' + ], + [ + '/test-image.jpg', + false, + true, + 2, + '<img src="{{media url="/test-image.jpg"}}" alt="" />' + ], + [ + 'test-image.jpg', + false, + true, + null, + '<img src="{{media url="test-image.jpg"}}" alt="" />' + ], + ]; + } +} diff --git a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php index c2e55029cab13..5527a39ce0507 100644 --- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php +++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php @@ -22,15 +22,17 @@ use Magento\Store\Model\StoreManager; use Magento\Store\Model\StoreManagerInterface; use Magento\TestFramework\Helper\Bootstrap; +use Magento\TestFramework\Helper\Xpath; use Magento\TestFramework\Mail\Template\TransportBuilderMock; use Magento\TestFramework\Request; +use Magento\TestFramework\TestCase\AbstractController; use Magento\Theme\Controller\Result\MessagePlugin; use PHPUnit\Framework\Constraint\StringContains; /** * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ -class AccountTest extends \Magento\TestFramework\TestCase\AbstractController +class AccountTest extends AbstractController { /** * @var TransportBuilderMock @@ -54,9 +56,8 @@ protected function setUp(): void */ protected function login($customerId) { - /** @var \Magento\Customer\Model\Session $session */ - $session = Bootstrap::getObjectManager() - ->get(\Magento\Customer\Model\Session::class); + /** @var Session $session */ + $session = Bootstrap::getObjectManager()->get(Session::class); $session->loginById($customerId); } @@ -148,8 +149,8 @@ public function testCreatepasswordActionWithSession() $customer->setData('confirmation', 'confirmation'); $customer->save(); - /** @var \Magento\Customer\Model\Session $customer */ - $session = Bootstrap::getObjectManager()->get(\Magento\Customer\Model\Session::class); + /** @var Session $customer */ + $session = Bootstrap::getObjectManager()->get(Session::class); $session->setRpToken($token); $session->setRpCustomerId($customer->getId()); @@ -404,18 +405,16 @@ public function testEditAction() $this->assertEquals(200, $this->getResponse()->getHttpResponseCode(), $body); $this->assertStringContainsString('<div class="field field-name-firstname required">', $body); // Verify the password check box is not checked - $expectedString = <<<EXPECTED_HTML -<input type="checkbox" name="change_password" id="change-password" data-role="change-password" value="1" - title="Change Password" - class="checkbox" /> -EXPECTED_HTML; - $this->assertStringContainsString($expectedString, $body); + $checkboxXpath = '//input[@type="checkbox"][@name="change_password"][@id="change-password"][not (@checked)]' . + '[@data-role="change-password"][@value="1"][@title="Change Password"][@class="checkbox"]'; + + $this->assertEquals(1, Xpath::getElementsCountForXpath($checkboxXpath, $body)); } /** * @magentoDataFixture Magento/Customer/_files/customer.php */ - public function testChangePasswordEditAction() + public function testChangePasswordEditAction(): void { $this->login(1); @@ -425,12 +424,11 @@ public function testChangePasswordEditAction() $this->assertEquals(200, $this->getResponse()->getHttpResponseCode(), $body); $this->assertStringContainsString('<div class="field field-name-firstname required">', $body); // Verify the password check box is checked - $expectedString = <<<EXPECTED_HTML -<input type="checkbox" name="change_password" id="change-password" data-role="change-password" value="1" - title="Change Password" - checked="checked" class="checkbox" /> -EXPECTED_HTML; - $this->assertStringContainsString($expectedString, $body); + $checkboxXpath = '//input[@type="checkbox"][@name="change_password"][@id="change-password"]' . + '[@data-role="change-password"][@value="1"][@title="Change Password"][@checked="checked"]' . + '[@class="checkbox"]'; + + $this->assertEquals(1, Xpath::getElementsCountForXpath($checkboxXpath, $body)); } /** diff --git a/dev/tests/integration/testsuite/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByTokenTest.php b/dev/tests/integration/testsuite/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByTokenTest.php new file mode 100644 index 0000000000000..55e9a9572d229 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Customer/Model/ForgotPasswordToken/GetCustomerByTokenTest.php @@ -0,0 +1,42 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Customer\Model\ForgotPasswordToken; + +use Magento\Framework\Exception\NoSuchEntityException; +use Magento\Framework\ObjectManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +class GetCustomerByTokenTest extends TestCase +{ + private const RESET_PASSWORD = '8ed8677e6c79e68b94e61658bd756ea5'; + + /** @var ObjectManagerInterface */ + private $objectManager; + + /** + * @var GetCustomerByToken + */ + private $customerByToken; + + protected function setUp(): void + { + $this->objectManager = Bootstrap::getObjectManager(); + $this->customerByToken = $this->objectManager->get(GetCustomerByToken::class); + } + + /** + * @magentoDataFixture Magento/Customer/_files/customer.php + */ + public function testExecuteWithNoSuchEntityException(): void + { + self::expectException(NoSuchEntityException::class); + self::expectExceptionMessage('No such entity with rp_token = ' . self::RESET_PASSWORD); + $this->customerByToken->execute(self::RESET_PASSWORD); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php new file mode 100644 index 0000000000000..52e7191a97226 --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/Model/SynchronizeFilesTest.php @@ -0,0 +1,151 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\MediaGallerySynchronizationMetadata\Model; + +use Magento\Framework\App\Filesystem\DirectoryList; +use Magento\Framework\Exception\FileSystemException; +use Magento\Framework\Filesystem; +use Magento\Framework\Filesystem\Directory\WriteInterface; +use Magento\Framework\Filesystem\DriverInterface; +use Magento\MediaGalleryApi\Api\Data\AssetInterface; +use Magento\MediaGalleryApi\Api\Data\KeywordInterface; +use Magento\MediaGalleryApi\Api\GetAssetsByPathsInterface; +use Magento\MediaGallerySynchronizationApi\Api\SynchronizeFilesInterface; +use Magento\MediaGalleryApi\Api\GetAssetsKeywordsInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for SynchronizeFiles. + */ +class SynchronizeFilesTest extends TestCase +{ + /** + * @var DriverInterface + */ + private $driver; + + /** + * @var SynchronizeFilesInterface + */ + private $synchronizeFiles; + + /** + * @var GetAssetsByPathsInterface + */ + private $getAssetsByPath; + + /** + * @var WriteInterface + */ + private $mediaDirectory; + + /** + * @var GetAssetsKeywordsInterface + */ + private $getAssetKeywords; + + /** + * @inheritdoc + */ + protected function setUp(): void + { + $this->driver = Bootstrap::getObjectManager()->get(DriverInterface::class); + $this->synchronizeFiles = Bootstrap::getObjectManager()->get(SynchronizeFilesInterface::class); + $this->getAssetsByPath = Bootstrap::getObjectManager()->get(GetAssetsByPathsInterface::class); + $this->getAssetKeywords = Bootstrap::getObjectManager()->get(GetAssetsKeywordsInterface::class); + $this->mediaDirectory = Bootstrap::getObjectManager()->get(Filesystem::class) + ->getDirectoryWrite(DirectoryList::MEDIA); + } + + /** + * Test for SynchronizeFiles::execute + * + * @dataProvider filesProvider + * @param null|string $file + * @param null|string $title + * @param null|string $description + * @param null|array $keywords + * @throws FileSystemException + * @throws \Magento\Framework\Exception\LocalizedException + */ + public function testExecute( + ?string $file, + ?string $title, + ?string $description, + ?array $keywords + ): void { + $path = realpath(__DIR__ . '/../_files/' . $file); + $modifiableFilePath = $this->mediaDirectory->getAbsolutePath($file); + $this->driver->copy( + $path, + $modifiableFilePath + ); + + $this->synchronizeFiles->execute([$file]); + + $loadedAssets = $this->getAssetsByPath->execute([$file])[0]; + $loadedKeywords = $this->getKeywords($loadedAssets) ?: null; + + $this->assertEquals($title, $loadedAssets->getTitle()); + $this->assertEquals($description, $loadedAssets->getDescription()); + $this->assertEquals($keywords, $loadedKeywords); + + $this->driver->deleteFile($modifiableFilePath); + } + + /** + * Data provider for testExecute + * + * @return array[] + */ + public function filesProvider(): array + { + return [ + [ + '/magento.jpg', + 'magento', + null, + null + ], + [ + '/magento_metadata.jpg', + 'Title of the magento image', + 'Description of the magento image', + [ + 'magento', + 'mediagallerymetadata' + ] + ] + ]; + } + + /** + * Key asset keywords + * + * @param AssetInterface $asset + * @return string[] + */ + private function getKeywords(AssetInterface $asset): array + { + $assetKeywords = $this->getAssetKeywords->execute([$asset->getId()]); + + if (empty($assetKeywords)) { + return []; + } + + $keywords = current($assetKeywords)->getKeywords(); + + return array_map( + function (KeywordInterface $keyword) { + return $keyword->getKeyword(); + }, + $keywords + ); + } +} diff --git a/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento.jpg b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento.jpg new file mode 100644 index 0000000000000..c377daf8fb0b3 Binary files /dev/null and b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento.jpg differ diff --git a/app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_metadata.jpg b/dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento_metadata.jpg similarity index 100% rename from app/code/Magento/MediaGallerySynchronization/Test/Integration/_files/magento_metadata.jpg rename to dev/tests/integration/testsuite/Magento/MediaGallerySynchronizationMetadata/_files/magento_metadata.jpg diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php index 370dc552458b9..dbf8bce795548 100644 --- a/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php +++ b/dev/tests/integration/testsuite/Magento/Newsletter/Model/SubscriberTest.php @@ -8,6 +8,7 @@ namespace Magento\Newsletter\Model; use Magento\Customer\Api\CustomerRepositoryInterface; +use Magento\Framework\Mail\EmailMessage; use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\ObjectManagerInterface; @@ -22,13 +23,16 @@ */ class SubscriberTest extends TestCase { - /** @var ObjectManagerInterface */ + private const CONFIRMATION_SUBSCRIBE = 'You have been successfully subscribed to our newsletter.'; + private const CONFIRMATION_UNSUBSCRIBE = 'You have been unsubscribed from the newsletter.'; + + /** @var ObjectManagerInterface */ private $objectManager; /** @var SubscriberFactory */ private $subscriberFactory; - /** @var TransportBuilderMock */ + /** @var TransportBuilderMock */ private $transportBuilder; /** @var CustomerRepositoryInterface */ @@ -89,27 +93,20 @@ public function testUnsubscribeSubscribe(): void $subscriber = $this->subscriberFactory->create(); $this->assertSame($subscriber, $subscriber->loadByCustomerId(1)); $this->assertEquals($subscriber, $subscriber->unsubscribe()); - $this->assertStringContainsString( - 'You have been unsubscribed from the newsletter.', - $this->getFilteredRawMessage($this->transportBuilder) + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_UNSUBSCRIBE, + $this->transportBuilder->getSentMessage() ); + $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $subscriber->getSubscriberStatus()); // Subscribe and verify $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->subscribe('customer@example.com')); $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getSubscriberStatus()); - $this->assertStringContainsString( - 'You have been successfully subscribed to our newsletter.', - $this->getFilteredRawMessage($this->transportBuilder) - ); - } - /** - * @param TransportBuilderMock $transportBuilderMock - * @return string - */ - private function getFilteredRawMessage(TransportBuilderMock $transportBuilderMock): string - { - return $transportBuilderMock->getSentMessage()->getBody()->getParts()[0]->getRawContent(); + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_SUBSCRIBE, + $this->transportBuilder->getSentMessage() + ); } /** @@ -125,16 +122,17 @@ public function testUnsubscribeSubscribeByCustomerId(): void // Unsubscribe and verify $this->assertSame($subscriber, $subscriber->unsubscribeCustomerById(1)); $this->assertEquals(Subscriber::STATUS_UNSUBSCRIBED, $subscriber->getSubscriberStatus()); - $this->assertStringContainsString( - 'You have been unsubscribed from the newsletter.', - $this->getFilteredRawMessage($this->transportBuilder) + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_UNSUBSCRIBE, + $this->transportBuilder->getSentMessage() ); + // Subscribe and verify $this->assertSame($subscriber, $subscriber->subscribeCustomerById(1)); $this->assertEquals(Subscriber::STATUS_SUBSCRIBED, $subscriber->getSubscriberStatus()); - $this->assertStringContainsString( - 'You have been successfully subscribed to our newsletter.', - $this->getFilteredRawMessage($this->transportBuilder) + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_SUBSCRIBE, + $this->transportBuilder->getSentMessage() ); } @@ -152,9 +150,10 @@ public function testConfirm(): void $subscriber->subscribe($customerEmail); $subscriber->loadByEmail($customerEmail); $subscriber->confirm($subscriber->getSubscriberConfirmCode()); - $this->assertStringContainsString( - 'You have been successfully subscribed to our newsletter.', - $this->getFilteredRawMessage($this->transportBuilder) + + $this->assertConfirmationParagraphExists( + self::CONFIRMATION_SUBSCRIBE, + $this->transportBuilder->getSentMessage() ); } @@ -189,4 +188,35 @@ public function testSubscribeUnconfirmedCustomerWithoutSubscription(): void $subscriber->subscribeCustomerById($customer->getId()); $this->assertEquals(Subscriber::STATUS_UNCONFIRMED, $subscriber->getStatus()); } + + /** + * Verifies if Paragraph with specified message is in e-mail + * + * @param string $expectedMessage + * @param EmailMessage $message + */ + private function assertConfirmationParagraphExists(string $expectedMessage, EmailMessage $message): void + { + $messageContent = $this->getMessageRawContent($message); + + $emailDom = new \DOMDocument(); + $emailDom->loadHTML($messageContent); + + $emailXpath = new \DOMXPath($emailDom); + $greeting = $emailXpath->query("//p[contains(text(), '$expectedMessage')]"); + + $this->assertSame(1, $greeting->length, "Cannot find the confirmation paragraph in e-mail contents"); + } + + /** + * Returns raw content of provided message + * + * @param EmailMessage $message + * @return string + */ + private function getMessageRawContent(EmailMessage $message): string + { + $emailParts = $message->getBody()->getParts(); + return current($emailParts)->getRawContent(); + } } diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php index e1cc942d4ae28..3e6b27a7ca622 100644 --- a/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php +++ b/dev/tests/integration/testsuite/Magento/Sales/Model/AdminOrder/CreateTest.php @@ -684,7 +684,6 @@ public function testMoveQuoteItemToCart() public function testGetCustomerCartNewCart() { $customerIdFromFixture = 1; - $customerEmailFromFixture = 'customer@example.com'; /** Preconditions */ /** @var SessionQuote $session */ @@ -693,12 +692,8 @@ public function testGetCustomerCartNewCart() /** SUT execution */ $customerQuote = $this->model->getCustomerCart(); - self::assertNotEmpty($customerQuote->getId(), 'Quote ID is invalid.'); - self::assertEquals( - $customerEmailFromFixture, - $customerQuote->getCustomerEmail(), - 'Customer data is preserved incorrectly in a newly quote.' - ); + self::assertInstanceOf(Quote::class, $customerQuote); + self::assertEmpty($customerQuote->getData()); } /** diff --git a/dev/tests/integration/testsuite/Magento/Store/Ui/Component/Listing/Column/Store/OptionsTest.php b/dev/tests/integration/testsuite/Magento/Store/Ui/Component/Listing/Column/Store/OptionsTest.php new file mode 100644 index 0000000000000..e13c4a427464f --- /dev/null +++ b/dev/tests/integration/testsuite/Magento/Store/Ui/Component/Listing/Column/Store/OptionsTest.php @@ -0,0 +1,114 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Store\Ui\Component\Listing\Column\Store; + +use Magento\Store\Model\ResourceModel\Group as GroupResource; +use Magento\Store\Model\ResourceModel\Store as StoreResource; +use Magento\Store\Model\ResourceModel\Website as WebsiteResource; +use Magento\Store\Model\StoreManagerInterface; +use Magento\TestFramework\Helper\Bootstrap; +use PHPUnit\Framework\TestCase; + +/** + * Test for \Magento\Store\Ui\Component\Listing\Column\Store\Options. + */ +class OptionsTest extends TestCase +{ + private const DEFAULT_WEBSITE_NAME = 'Main Website'; + private const DEFAULT_STORE_GROUP_NAME = 'Main Website Store'; + private const DEFAULT_STORE_NAME = 'Default Store View'; + + /** + * @var OptionsFactory + */ + private $modelFactory; + + /** + * @var StoreManagerInterface + */ + private $storeManager; + + /** + * @var WebsiteResource + */ + private $websiteResource; + + /** + * @var StoreResource + */ + private $storeResource; + + /** + * @var GroupResource + */ + private $groupResource; + + /** + * @return void + */ + protected function setUp(): void + { + $objectManager = Bootstrap::getObjectManager(); + + $this->modelFactory = $objectManager->get(OptionsFactory::class); + $this->storeManager = $objectManager->get(StoreManagerInterface::class); + + $this->websiteResource = $objectManager->get(WebsiteResource::class); + $this->groupResource = $objectManager->get(GroupResource::class); + $this->storeResource = $objectManager->get(StoreResource::class); + } + + /** + * To option array test with duplicate website, store group, store view names + * + * @magentoDataFixture Magento/Store/_files/second_website_with_store_group_and_store.php + * + * @return void + */ + public function testToOptionArray(): void + { + $website = $this->storeManager->getWebsite('test'); + $this->websiteResource->save($website->setName(self::DEFAULT_WEBSITE_NAME)); + + $storeGroup = current($website->getGroups()); + $this->groupResource->save($storeGroup->setName(self::DEFAULT_STORE_GROUP_NAME)); + + $store = current($website->getStores()); + $this->storeResource->save($store->setName(self::DEFAULT_STORE_NAME)); + + $model = $this->modelFactory->create(); + $storeIds = [$this->storeManager->getStore('default')->getId(), $store->getId()]; + + $this->assertEquals($this->getExpectedOptions($storeIds), $model->toOptionArray()); + } + + /** + * Returns expected options + * + * @param array $storeIds + * @return array + */ + private function getExpectedOptions(array $storeIds): array + { + $expectedOptions = []; + foreach ($storeIds as $storeId) { + $expectedOptions[] = [ + 'label' => self::DEFAULT_WEBSITE_NAME, + 'value' => [[ + 'label' => str_repeat(' ', 4) . self::DEFAULT_STORE_GROUP_NAME, + 'value' => [[ + 'label' => str_repeat(' ', 8) . self::DEFAULT_STORE_NAME, + 'value' => $storeId, + ]], + ]], + ]; + } + + return $expectedOptions; + } +} diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.test.js index 546392d35fe84..a555f8e00a916 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/dynamic-rows-configurable.test.js @@ -41,11 +41,9 @@ define([ }) }; - model.getChildItems = jasmine.createSpy().and.returnValue($('')); model.source = sourceMock; model.processingUnionInsertData(mockData); expect(model.source.get).toHaveBeenCalled(); - expect(model.getChildItems).toHaveBeenCalled(); expect(expectedData[1].sku).toBe('Conf&-sdfs'); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js index ae497004e59de..e82b2261861d4 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Customer/frontend/js/customer-data.test.js @@ -5,35 +5,119 @@ /* global _ */ /* eslint max-nested-callbacks: 0 */ +/* jscs:disable jsDoc*/ + define([ 'squire', 'jquery', + 'Magento_Customer/js/section-config', + 'Magento_Customer/js/customer-data', 'jquery/jquery-storageapi' -], function (Squire, $) { +], function (Squire, $, sectionConfig, customerData) { 'use strict'; var injector = new Squire(), - sectionConfig, + obj, originalGetJSON, originalReload, + originalIsEmpty, originalEach, - obj; + cookieLifeTime = 3600, + sectionConfigSettings = { + baseUrls: [ + 'http://localhost/' + ], + sections: { + 'customer/account/loginpost': ['*'], + 'checkout/cart/add': ['cart'], + 'rest/*/v1/guest-carts/*/selected-payment-method': ['cart', 'checkout-data'], + '*': ['messages'] + }, + clientSideSections: [ + 'checkout-data', + 'cart-data' + ], + sectionNames: [ + 'customer', + 'product_data_storage', + 'cart', + 'messages' + ] + }; + + function init(config) { + var defaultConfig = { + sectionLoadUrl: 'http://localhost/customer/section/load/', + expirableSectionLifetime: 60, // minutes + expirableSectionNames: ['cart'], + cookieLifeTime: cookieLifeTime, + updateSessionUrl: 'http://localhost/customer/account/updateSession/' + }; + + customerData['Magento_Customer/js/customer-data']($.extend({}, defaultConfig, config || {})); + } + + function setupLocalStorage(sections) { + var mageCacheStorage = {}, + sectionDataIds = {}; + + _.each(sections, function (sectionData, sectionName) { + sectionDataIds[sectionName] = sectionData['data_id']; + + if (typeof sectionData.content !== 'undefined') { + mageCacheStorage[sectionName] = sectionData; + } + }); + + $.localStorage.set( + 'mage-cache-storage', + mageCacheStorage + ); + $.cookieStorage.set( + 'section_data_ids', + sectionDataIds + ); + + $.localStorage.set( + 'mage-cache-timeout', + new Date(Date.now() + cookieLifeTime * 1000) + ); + $.cookieStorage.set( + 'mage-cache-sessid', + true + ); + } + + function clearLocalStorage() { + $.cookieStorage.set('section_data_ids', {}); + + if (window.localStorage) { + window.localStorage.clear(); + } + } describe('Magento_Customer/js/customer-data', function () { + beforeAll(function () { + clearLocalStorage(); + }); + beforeEach(function (done) { + originalGetJSON = jQuery.getJSON; + sectionConfig['Magento_Customer/js/section-config'](sectionConfigSettings); + injector.require([ - 'Magento_Customer/js/customer-data', - 'Magento_Customer/js/section-config' - ], function (Constr, sectionConfiguration) { + 'Magento_Customer/js/customer-data' + ], function (Constr) { obj = Constr; - sectionConfig = sectionConfiguration; done(); }); }); afterEach(function () { try { + jQuery.getJSON = originalGetJSON; + clearLocalStorage(); injector.clean(); injector.remove(); } catch (e) { @@ -41,49 +125,19 @@ define([ }); describe('"init" method', function () { - var storageInvalidation = { - /** - * Mock Keys Method - * @returns array - */ - keys: function () { - return ['section']; - } - }, - dataProvider = { - /** - * Mock getFromStorage Method - * @returns array - */ - getFromStorage: function () { - return ['section']; - } - }, - storage = { - /** - * Mock Keys Method - * @returns array - */ - keys: function () { - return ['section']; - } - }; - beforeEach(function () { originalReload = obj.reload; + originalIsEmpty = _.isEmpty; + spyOn(obj, 'reload').and.returnValue(true); - //Init storage api library $.initNamespaceStorage('mage-cache-storage').localStorage; $.initNamespaceStorage('mage-cache-storage-section-invalidation').localStorage; - - spyOn(dataProvider, 'getFromStorage'); - spyOn(storage, 'keys').and.returnValue(['section']); - spyOn(storageInvalidation, 'keys').and.returnValue(['section']); }); afterEach(function () { obj.reload = originalReload; + _.isEmpty = originalIsEmpty; $.namespaceStorages = {}; }); @@ -113,15 +167,68 @@ define([ it('Calls "reload" method when expired sections do not exist', function () { spyOn(obj, 'getExpiredSectionNames').and.returnValue([]); - - _.isEmpty = jasmine.createSpy().and.returnValue(false); + _.isEmpty = jasmine.createSpy('_.isEmpty').and.returnValue(false); obj.init(); expect(obj.reload).toHaveBeenCalled(); }); + + it('Check it does not request sections from the server if there are no expired sections', function () { + setupLocalStorage({ + 'catalog': { // without storage content + 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute + } + }); + + jQuery.getJSON = jasmine.createSpy().and.callFake(function () { + var deferred = $.Deferred(); + + return deferred.promise(); + }); + + init(); + expect(jQuery.getJSON).not.toHaveBeenCalled(); + }); + + it('Check it requests sections from the server if there are expired sections', function () { + setupLocalStorage({ + 'customer': { + 'data_id': Math.floor(Date.now() / 1000) + 60 // invalidated, + }, + 'cart': { + 'data_id': Math.floor(Date.now() / 1000) - 61 * 60, // 61 minutes ago + 'content': {} + }, + 'product_data_storage': { + 'data_id': Math.floor(Date.now() / 1000) + 60, // in 1 minute + 'content': {} + }, + 'catalog': { + 'data_id': Math.floor(Date.now() / 1000) + 60 // invalid section, + }, + 'checkout': { + 'data_id': Math.floor(Date.now() / 1000) - 61 * 60, // invalid section, + 'content': {} + } + }); + + $.getJSON = jasmine.createSpy('$.getJSON').and.callFake(function () { + var deferred = $.Deferred(); + + return deferred.promise(); + }); + + init(); + expect($.getJSON).toHaveBeenCalledWith( + 'http://localhost/customer/section/load/', + jasmine.objectContaining({ + sections: 'cart,customer' + }) + ); + }); }); - describe('"getExpiredSectionNames" method', function () { + describe('"getExpiredSectionNames method', function () { it('Should be defined', function () { expect(obj.hasOwnProperty('getExpiredSectionNames')).toBeDefined(); }); @@ -131,6 +238,50 @@ define([ obj.getExpiredSectionNames(); }).not.toThrow(); }); + + it('Check that result contains expired section names', function () { + setupLocalStorage({ + 'cart': { + 'data_id': Math.floor(Date.now() / 1000) - 61 * 60, // 61 minutes ago + 'content': {} + } + }); + init(); + expect(customerData.getExpiredSectionNames()).toEqual(['cart']); + }); + + it('Check that result does not contain unexpired section names', function () { + setupLocalStorage({ + 'cart': { + 'data_id': Math.floor(Date.now() / 1000) + 60, // in 1 minute + 'content': {} + } + }); + init(); + expect(customerData.getExpiredSectionNames()).toEqual([]); + }); + + it('Check that result contains invalidated section names', function () { + setupLocalStorage({ + 'cart': { // without storage content + 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute + } + }); + + init(); + expect(customerData.getExpiredSectionNames()).toEqual(['cart']); + }); + + it('Check that result does not contain unsupported section names', function () { + setupLocalStorage({ + 'catalog': { // without storage content + 'data_id': Math.floor(Date.now() / 1000) + 60 // in 1 minute + } + }); + + init(); + expect(customerData.getExpiredSectionNames()).toEqual([]); + }); }); describe('"get" method', function () { @@ -169,7 +320,6 @@ define([ describe('"reload" method', function () { beforeEach(function () { - originalGetJSON = jQuery.getJSON; jQuery.getJSON = jasmine.createSpy().and.callFake(function () { var deferred = $.Deferred(); @@ -189,10 +339,6 @@ define([ }); }); - afterEach(function () { - jQuery.getJSON = originalGetJSON; - }); - it('Should be defined', function () { expect(obj.hasOwnProperty('reload')).toBeDefined(); }); @@ -203,7 +349,7 @@ define([ }).not.toThrow(); }); - it('Returns proper sections object when passed array with a single section name', function () { + it('Check it returns proper sections object when passed array with a single section name', function () { var result; spyOn(sectionConfig, 'filterClientSideSections').and.returnValue(['section']); @@ -239,7 +385,7 @@ define([ })); }); - it('Returns proper sections object when passed array with a multiple section names', function () { + it('Check it returns proper sections object when passed array with multiple section names', function () { var result; spyOn(sectionConfig, 'filterClientSideSections').and.returnValue(['cart,customer,messages']); @@ -278,8 +424,8 @@ define([ } })); }); - - it('Returns all sections when passed wildcard string', function () { + // + it('Check it returns all sections when passed wildcard string', function () { var result; jQuery.getJSON = jasmine.createSpy().and.callFake(function (url, parameters) { @@ -319,7 +465,7 @@ define([ }); }); - describe('"invalidate" method', function () { + describe('"invalidated" method', function () { it('Should be defined', function () { expect(obj.hasOwnProperty('invalidate')).toBeDefined(); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/image-preview.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/image-preview.test.js index 6a466f0c37872..a5b434d956097 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/image-preview.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/columns/image-preview.test.js @@ -74,6 +74,7 @@ define([ originMock = $.fn.get; spyOn($.fn, 'get').and.returnValue(imageMock); + imagePreview.lastOpenedImage = jasmine.createSpy().and.returnValue(2); imagePreview.visibleRecord = jasmine.createSpy().and.returnValue(2); imagePreview.displayedRecord = ko.observable(); imagePreview.displayedRecord(recordMock); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js index 2c2cdab2d46da..7f7d0c5f9dd2a 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/masonry.test.js @@ -6,79 +6,78 @@ /*eslint max-nested-callbacks: 0*/ define([ 'jquery', - 'ko', 'Magento_Ui/js/grid/masonry' -], function ($, ko, Masonry) { +], function ($, Masonry) { 'use strict'; - var Component, - rows, - container = '<div data-id="masonry_grid" id="masonry_grid"><div class="masonry-image-column"></div></div>'; + describe('Magento_Ui/js/grid/masonry', function () { + var Component, + rows, + container = '<div data-id="masonry_grid" id="masonry_grid"><div class="masonry-image-column"></div></div>'; - beforeEach(function () { - rows = [ - { - _rowIndex: 0, - category: {}, - 'category_id': 695, - 'category_name': 'People', - 'comp_url': 'https://stock.adobe.com/Rest/Libraries/Watermarked/Download/327515738/2', - 'content_type': 'image/jpeg', - 'country_name': 'Malaysia', - 'creation_date': '2020-03-02 10:41:51', - 'creator_id': 208217780, - 'creator_name': 'NajmiArif', - height: 3264, - id: 327515738, - 'id_field_name': 'id', - 'is_downloaded': 0, - 'is_licensed_locally': 0, - keywords: [], - 'media_type_id': 1, - overlay: '', - path: '', - 'premium_level_id': 0, - 'thumbnail_240_url': 'https://t4.ftcdn.net/jpg/03/27/51/57/240_F_327515738_n.jpg', - 'thumbnail_500_ur': 'https://as2.ftcdn.net/jpg/03/27/51/57/500_F_327515738_n.jpg', - title: 'Neon effect picture of man wearing medical mask for viral or pandemic disease', - width: 4896 - } + beforeEach(function () { + rows = [ + { + _rowIndex: 0, + category: {}, + 'category_id': 695, + 'category_name': 'People', + 'comp_url': 'url', + 'content_type': 'image/jpeg', + 'country_name': 'Malaysia', + 'creation_date': '2020-03-02 10:41:51', + 'creator_id': 208217780, + 'creator_name': 'NajmiArif', + height: 3264, + id: 327515738, + 'id_field_name': 'id', + 'is_downloaded': 0, + 'is_licensed_locally': 0, + keywords: [], + 'media_type_id': 1, + overlay: '', + path: '', + 'premium_level_id': 0, + 'thumbnail_240_url': 'url', + 'thumbnail_500_ur': 'url', + title: 'Neon effect picture of man wearing medical mask for viral or pandemic disease', + width: 4896 + } + ]; - ]; - - $(container).appendTo('body'); - - Component = new Masonry({ - defaults: { - rows: ko.observable() - } + $(document.body).append(container); + Component = new Masonry({ + defaults: { + containerId: '#masonry_grid' + } + }); }); - }); - - afterEach(function () { - $('#masonry_grid').remove(); - }); + afterEach(function () { + Component.clear(); + $('#masonry_grid').remove(); + }); - describe('check initComponent', function () { - it('verify setLayoutstyles called and grid iniztilized', function () { - var setlayoutStyles = spyOn(Component, 'setLayoutStyles'); + describe('check initComponent', function () { + it('verify setLayoutstyles called and grid iniztilized', function () { + var setlayoutStyles = spyOn(Component, 'setLayoutStyles'); - expect(Component).toBeDefined(); - Component.containerId = 'masonry_grid'; - Component.initComponent(rows); - Component.rows().forEach(function (image) { - expect(image.styles).toBeDefined(); - expect(image.css).toBeDefined(); + expect(Component).toBeDefined(); + Component.containerId = 'masonry_grid'; + Component.initComponent(rows); + Component.rows().forEach(function (image) { + expect(image.styles).toBeDefined(); + expect(image.css).toBeDefined(); + }); + expect(setlayoutStyles).toHaveBeenCalled(); }); - expect(setlayoutStyles).toHaveBeenCalled(); - }); - it('verify events triggered', function () { - var setLayoutStyles = spyOn(Component, 'setLayoutStyles'); + it('verify events triggered', function () { + var setLayoutStyles = spyOn(Component, 'setLayoutStyles'); - Component.initComponent(rows); - window.dispatchEvent(new Event('resize')); - expect(setLayoutStyles).toHaveBeenCalled(); + Component.initComponent(rows); + window.dispatchEvent(new Event('resize')); + expect(setLayoutStyles).toHaveBeenCalled(); + }); }); }); }); diff --git a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/url-filter-applier.test.js b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/url-filter-applier.test.js index a3d49e382de51..1e63f9f61f6d1 100644 --- a/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/url-filter-applier.test.js +++ b/dev/tests/js/jasmine/tests/app/code/Magento/Ui/base/js/grid/url-filter-applier.test.js @@ -12,7 +12,8 @@ define([ describe('Magento_Ui/js/grid/url-filter-applier', function () { var urlFilterApplierObj, filterComponentMock = { - setData: jasmine.createSpy(), + set: jasmine.createSpy(), + get: jasmine.createSpy(), apply: jasmine.createSpy() }; @@ -64,11 +65,14 @@ define([ it('applies url filter on filter component', function () { urlFilterApplierObj.searchString = '?filters[name]=test&filters[qty]=1'; urlFilterApplierObj.apply(); - expect(urlFilterApplierObj.filterComponent().setData).toHaveBeenCalledWith({ - 'name': 'test', - 'qty': '1' - }, false); - expect(urlFilterApplierObj.filterComponent().apply).toHaveBeenCalled(); + expect(urlFilterApplierObj.filterComponent().get).toHaveBeenCalled(); + expect(urlFilterApplierObj.filterComponent().set).toHaveBeenCalledWith( + 'applied', + { + 'name': 'test', + 'qty': '1' + } + ); }); }); }); diff --git a/dev/tests/static/framework/Magento/TestFramework/Utility/AddedFiles.php b/dev/tests/static/framework/Magento/TestFramework/Utility/AddedFiles.php new file mode 100644 index 0000000000000..4afeda3a035eb --- /dev/null +++ b/dev/tests/static/framework/Magento/TestFramework/Utility/AddedFiles.php @@ -0,0 +1,35 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Utility; + +/** + * Helper class to add list of added new files. + */ +class AddedFiles +{ + /** + * Provide list of new files. + * + * @param string $changedFilesBaseDir + * + * @return string[] + */ + public static function getAddedFilesList(string $changedFilesBaseDir): array + { + return FilesSearch::getFilesFromListFile( + $changedFilesBaseDir, + 'changed_files*.added.*', + function () { + // if no list files, probably, this is the dev environment + // phpcs:ignore Generic.PHP.NoSilencedErrors,Magento2.Security.InsecureFunction + @exec('git diff --cached --name-only --diff-filter=A', $addedFiles); + return $addedFiles; + } + ); + } +} diff --git a/dev/tests/static/framework/Magento/TestFramework/Utility/ChildrenClassesSearch.php b/dev/tests/static/framework/Magento/TestFramework/Utility/ChildrenClassesSearch.php new file mode 100644 index 0000000000000..53db8ca8d0b08 --- /dev/null +++ b/dev/tests/static/framework/Magento/TestFramework/Utility/ChildrenClassesSearch.php @@ -0,0 +1,56 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Utility; + +/** + * Search for children classes in list of files. + */ +class ChildrenClassesSearch +{ + /** + * @var ClassNameExtractor + */ + private $classNameExtractor; + + /** + * ChildrenClassesSearch constructor. + */ + public function __construct() + { + $this->classNameExtractor = new ClassNameExtractor(); + } + + /** + * Get list of classes name which are subclasses of mentioned class. + * + * @param array $fileList + * @param string $parent + * @param bool $asDataSet + * + * @return array + * @throws \ReflectionException + */ + public function getClassesWhichAreChildrenOf(array $fileList, string $parent, bool $asDataSet = true): array + { + $found = []; + + foreach ($fileList as $file) { + $name = $asDataSet ? $file[0] : $file; + $class = $this->classNameExtractor->getNameWithNamespace(file_get_contents($name)); + + if ($class) { + $classReflection = new \ReflectionClass($class); + if ($classReflection->isSubclassOf($parent)) { + $found[] = $class; + } + } + } + + return $found; + } +} diff --git a/dev/tests/static/framework/Magento/TestFramework/Utility/FilesSearch.php b/dev/tests/static/framework/Magento/TestFramework/Utility/FilesSearch.php new file mode 100644 index 0000000000000..0ec8124496d1d --- /dev/null +++ b/dev/tests/static/framework/Magento/TestFramework/Utility/FilesSearch.php @@ -0,0 +1,48 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Utility; + +/** + * Helper class to search files by provided directory and file pattern. + */ +class FilesSearch +{ + /** + * Read files from generated lists. + * + * @param string $listsBaseDir + * @param string $listFilePattern + * @param callable $noListCallback + * @return string[] + */ + public static function getFilesFromListFile( + string $listsBaseDir, + string $listFilePattern, + callable $noListCallback + ): array { + $filesDefinedInList = []; + $listFiles = glob($listsBaseDir . '/_files/' . $listFilePattern); + if (!empty($listFiles)) { + foreach ($listFiles as $listFile) { + $filesDefinedInList[] = file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + } + $filesDefinedInList = array_merge([], ...$filesDefinedInList); + } else { + $filesDefinedInList = call_user_func($noListCallback); + } + array_walk( + $filesDefinedInList, + function (&$file) { + $file = BP . '/' . $file; + } + ); + $filesDefinedInList = array_values(array_unique($filesDefinedInList)); + + return $filesDefinedInList; + } +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/A.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/A.php new file mode 100644 index 0000000000000..3369df1edd54f --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/A.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\TestFramework\Utility\ChildrenClassesSearch; + +class A +{ +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/B.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/B.php new file mode 100644 index 0000000000000..8873af3e4f9b7 --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/B.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\TestFramework\Utility\ChildrenClassesSearch; + +class B extends A +{ +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/C.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/C.php new file mode 100644 index 0000000000000..e50735fcbb46c --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/C.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\TestFramework\Utility\ChildrenClassesSearch; + +class C implements ZInterface +{ +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/D.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/D.php new file mode 100644 index 0000000000000..8dc2cc0b9269e --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/D.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\TestFramework\Utility\ChildrenClassesSearch; + +class D +{ +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/E.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/E.php new file mode 100644 index 0000000000000..563135ff36d3c --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/E.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\TestFramework\Utility\ChildrenClassesSearch; + +class E extends B +{ +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/F.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/F.php new file mode 100644 index 0000000000000..6976ed26a0d84 --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/F.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\TestFramework\Utility\ChildrenClassesSearch; + +class F extends E implements ZInterface +{ +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/ZInterface.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/ZInterface.php new file mode 100644 index 0000000000000..be1b6d222519c --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearch/ZInterface.php @@ -0,0 +1,10 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +namespace Magento\TestFramework\Utility\ChildrenClassesSearch; + +interface ZInterface +{ +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearchTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearchTest.php new file mode 100644 index 0000000000000..5b7fd47042347 --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/ChildrenClassesSearchTest.php @@ -0,0 +1,54 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Utility; + +use Magento\TestFramework\Utility\ChildrenClassesSearch\A; +use Magento\TestFramework\Utility\ChildrenClassesSearch\B; +use Magento\TestFramework\Utility\ChildrenClassesSearch\E; +use Magento\TestFramework\Utility\ChildrenClassesSearch\F; +use PHPUnit\Framework\TestCase; + +class ChildrenClassesSearchTest extends TestCase +{ + /** + * @var ChildrenClassesSearch + */ + private $childrenClassesSearch; + + protected function setUp(): void + { + $this->childrenClassesSearch = new ChildrenClassesSearch(); + } + + public function testChildrenSearch(): void + { + $files = [ + __DIR__ . '/ChildrenClassesSearch/A.php', + __DIR__ . '/ChildrenClassesSearch/B.php', + __DIR__ . '/ChildrenClassesSearch/C.php', + __DIR__ . '/ChildrenClassesSearch/D.php', + __DIR__ . '/ChildrenClassesSearch/E.php', + __DIR__ . '/ChildrenClassesSearch/F.php', + __DIR__ . '/ChildrenClassesSearch/ZInterface.php', + ]; + + $found = $this->childrenClassesSearch->getClassesWhichAreChildrenOf( + $files, + A::class, + false + ); + + $expected = [ + B::class, + E::class, + F::class + ]; + + $this->assertSame($expected, $found); + } +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/FilesSearchTest.php b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/FilesSearchTest.php new file mode 100644 index 0000000000000..7b27dde3b0bf4 --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/FilesSearchTest.php @@ -0,0 +1,53 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\TestFramework\Utility; + +use PHPUnit\Framework\TestCase; + +class FilesSearchTest extends TestCase +{ + /** + * Test files list extraction from file. + */ + public function testGetFiles(): void + { + $pattern = 'changed_files*.txt'; + + $files = FilesSearch::getFilesFromListFile(__DIR__, $pattern, function () { + return []; + }); + + $expected = [ + BP . '/app/code/Magento/Cms/Block/Block.php', + BP . '/app/code/Magento/Cms/Api/BlockRepositoryInterface.php', + BP . '/app/code/Magento/Cms/Observer/NoCookiesObserver.php' + ]; + + $this->assertSame($files, $expected); + } + + /** + * Test callblack function in case when files with lists did not found. + */ + public function testGetEmptyList(): void + { + $pattern = 'zzz.txt'; + + $files = FilesSearch::getFilesFromListFile(__DIR__, $pattern, function () { + return ['1', '2', '3']; + }); + + $expected = [ + BP . '/1', + BP . '/2', + BP . '/3' + ]; + + $this->assertSame($files, $expected); + } +} diff --git a/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/_files/changed_files_some_name_test.txt b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/_files/changed_files_some_name_test.txt new file mode 100644 index 0000000000000..816f4b32c9361 --- /dev/null +++ b/dev/tests/static/framework/tests/unit/testsuite/Magento/TestFramework/Utility/_files/changed_files_some_name_test.txt @@ -0,0 +1,3 @@ +app/code/Magento/Cms/Block/Block.php +app/code/Magento/Cms/Api/BlockRepositoryInterface.php +app/code/Magento/Cms/Observer/NoCookiesObserver.php diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/Magento/Framework/App/Action/AbstractActionTest.php b/dev/tests/static/testsuite/Magento/Test/Legacy/Magento/Framework/App/Action/AbstractActionTest.php new file mode 100644 index 0000000000000..69050c3c51895 --- /dev/null +++ b/dev/tests/static/testsuite/Magento/Test/Legacy/Magento/Framework/App/Action/AbstractActionTest.php @@ -0,0 +1,85 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Test\Legacy\Magento\Framework\App\Action; + +use Magento\Framework\App\Action\AbstractAction; +use Magento\Framework\App\ActionInterface; +use Magento\Framework\App\Utility\Files; +use Magento\Framework\Exception\LocalizedException; +use Magento\TestFramework\Utility\AddedFiles; +use Magento\TestFramework\Utility\ChildrenClassesSearch; +use PHPUnit\Framework\TestCase; + +/** + * Test newly created controllers must do not extend AbstractAction. + */ +class AbstractActionTest extends TestCase +{ + /** + * @var ChildrenClassesSearch + */ + private $childrenClassesSearch; + + /** + * @var Files + */ + private $fileUtilities; + + /** + * @throws LocalizedException + */ + protected function setUp(): void + { + $this->childrenClassesSearch = new ChildrenClassesSearch(); + $this->fileUtilities = Files::init(); + } + + /** + * Test newly created controllers do not extend deprecated AbstractAction. + * + * @throws \ReflectionException + */ + public function testNewControllersDoNotExtendAbstractAction(): void + { + $files = $this->getTestFiles(); + + $found = $this->childrenClassesSearch->getClassesWhichAreChildrenOf($files, AbstractAction::class); + + $this->assertEmpty( + $found, + "The following new controller(s) extend " . AbstractAction::class . "\r\n" + . "All new controller classes must implement " . ActionInterface::class . " instead.\r\n" + . print_r($found, true) + ); + } + + /** + * Provide files for test. + * + * @return array + */ + private function getTestFiles(): array + { + $phpFiles = AddedFiles::getAddedFilesList($this->getChangedFilesBaseDir()); + + $phpFiles = Files::composeDataSets($phpFiles); + $fileTypes = Files::INCLUDE_APP_CODE | Files::INCLUDE_LIBS | Files::AS_DATA_SET; + return array_intersect_key($phpFiles, $this->fileUtilities->getPhpFiles($fileTypes)); + } + + /** + * Returns base directory for generated lists. + * + * @return string + */ + private function getChangedFilesBaseDir(): string + { + return BP . DIRECTORY_SEPARATOR . 'dev' . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'static' . + DIRECTORY_SEPARATOR . 'testsuite' . DIRECTORY_SEPARATOR . 'Magento' . DIRECTORY_SEPARATOR . 'Test'; + } +} diff --git a/dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php b/dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php index 324753b4bd4ec..ad91025448579 100644 --- a/dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php +++ b/dev/tests/static/testsuite/Magento/Test/Php/LiveCodeTest.php @@ -14,6 +14,8 @@ use Magento\TestFramework\CodingStandard\Tool\CopyPasteDetector; use Magento\TestFramework\CodingStandard\Tool\PhpCompatibility; use Magento\TestFramework\CodingStandard\Tool\PhpStan; +use Magento\TestFramework\Utility\AddedFiles; +use Magento\TestFramework\Utility\FilesSearch; use PHPMD\TextUI\Command; /** @@ -113,8 +115,8 @@ public static function getWhitelist( */ private static function getChangedFilesList($changedFilesBaseDir) { - return self::getFilesFromListFile( - $changedFilesBaseDir, + return FilesSearch::getFilesFromListFile( + $changedFilesBaseDir ?: self::getChangedFilesBaseDir(), 'changed_files*', function () { // if no list files, probably, this is the dev environment @@ -128,65 +130,6 @@ function () { ); } - /** - * This method loads list of added files. - * - * @param string $changedFilesBaseDir - * @return string[] - */ - private static function getAddedFilesList($changedFilesBaseDir) - { - return self::getFilesFromListFile( - $changedFilesBaseDir, - 'changed_files*.added.*', - function () { - // if no list files, probably, this is the dev environment - // phpcs:ignore Generic.PHP.NoSilencedErrors,Magento2.Security.InsecureFunction - @exec('git diff --cached --name-only --diff-filter=A', $addedFiles); - return $addedFiles; - } - ); - } - - /** - * Read files from generated lists. - * - * @param string $listsBaseDir - * @param string $listFilePattern - * @param callable $noListCallback - * @return string[] - */ - private static function getFilesFromListFile($listsBaseDir, $listFilePattern, $noListCallback) - { - $filesDefinedInList = []; - - $globFilesListPattern = ($listsBaseDir ?: self::getChangedFilesBaseDir()) - . '/_files/' . $listFilePattern; - $listFiles = glob($globFilesListPattern); - if (!empty($listFiles)) { - foreach ($listFiles as $listFile) { - // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge - $filesDefinedInList = array_merge( - $filesDefinedInList, - file($listFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) - ); - } - } else { - $filesDefinedInList = call_user_func($noListCallback); - } - - array_walk( - $filesDefinedInList, - function (&$file) { - $file = BP . '/' . $file; - } - ); - - $filesDefinedInList = array_values(array_unique($filesDefinedInList)); - - return $filesDefinedInList; - } - /** * Filter list of files. * @@ -427,7 +370,7 @@ public function testCopyPaste() */ public function testStrictTypes() { - $changedFiles = self::getAddedFilesList(''); + $changedFiles = AddedFiles::getAddedFilesList(self::getChangedFilesBaseDir()); try { $blackList = Files::init()->readLists( diff --git a/lib/internal/Magento/Framework/App/ResourceConnection.php b/lib/internal/Magento/Framework/App/ResourceConnection.php index 00dc88dcd7b17..3ba50fb396a4c 100644 --- a/lib/internal/Magento/Framework/App/ResourceConnection.php +++ b/lib/internal/Magento/Framework/App/ResourceConnection.php @@ -178,7 +178,7 @@ public function getTableName($modelEntity, $connectionName = self::DEFAULT_CONNE list($modelEntity, $tableSuffix) = $modelEntity; } - $tableName = $modelEntity; + $tableName = (string)$modelEntity; $mappedTableName = $this->getMappedTableName($tableName); if ($mappedTableName) { diff --git a/lib/internal/Magento/Framework/DB/Select.php b/lib/internal/Magento/Framework/DB/Select.php index 7d2799cf50679..0aaf29aeb332e 100644 --- a/lib/internal/Magento/Framework/DB/Select.php +++ b/lib/internal/Magento/Framework/DB/Select.php @@ -116,11 +116,11 @@ public function where($cond, $value = null, $type = null) { if ($value === null && $type === null) { $value = ''; - } elseif ($type == self::TYPE_CONDITION) { + } elseif ((string)$type === self::TYPE_CONDITION) { $type = null; } if (is_array($value)) { - $cond = $this->getConnection()->quoteInto($cond, $value); + $cond = $this->getConnection()->quoteInto($cond, $value, $type); $value = null; } return parent::where($cond, $value, $type); diff --git a/lib/internal/Magento/Framework/Data/Form/Element/Time.php b/lib/internal/Magento/Framework/Data/Form/Element/Time.php index 53d72d704483c..5f67ac4414e99 100644 --- a/lib/internal/Magento/Framework/Data/Form/Element/Time.php +++ b/lib/internal/Magento/Framework/Data/Form/Element/Time.php @@ -114,7 +114,7 @@ public function getElementHtml() 'style', [], <<<style - .select80wide { + select.select.select80wide { width: 80px; } style diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php index 20d017cc71062..073fb2e795e87 100644 --- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Type.php @@ -73,7 +73,15 @@ public function getFields() : array /** * Get interfaces the type implements, if any. Return an empty array if none are configured. * - * @return string[] + * Example return array( + * array( + * 'interface' => 'SomeDefinedTypeInterface', + * 'copyFields' => true + * ), + * ... + * ), + * + * @return array */ public function getInterfaces() : array { diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php new file mode 100644 index 0000000000000..02d0a60064db6 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionFactory.php @@ -0,0 +1,69 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +use Magento\Framework\GraphQl\Config\ConfigElementFactoryInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; +use Magento\Framework\ObjectManagerInterface; + +/** + * Factory for config elements of 'union' type. + */ +class UnionFactory implements ConfigElementFactoryInterface +{ + /** + * @var ObjectManagerInterface + */ + private $objectManager; + + /** + * @param ObjectManagerInterface $objectManager + */ + public function __construct( + ObjectManagerInterface $objectManager + ) { + $this->objectManager = $objectManager; + } + + /** + * Instantiate an object representing 'union' GraphQL config element. + * + * @param array $data + * @return ConfigElementInterface + */ + public function createFromConfigData(array $data): ConfigElementInterface + { + return $this->create($data, $data['types'] ?? []); + } + + /** + * Create union object based off array of configured GraphQL. + * + * Union data must contain name, type resolver, and possible concrete types definitions + * The type resolver should point to an implementation of the TypeResolverInterface + * that decides what concrete GraphQL type to output. Description is the only optional field. + * + * @param array $unionData + * @param array $types + * @return UnionType + */ + public function create( + array $unionData, + array $types + ) : UnionType { + return $this->objectManager->create( + UnionType::class, + [ + 'name' => $unionData['name'], + 'typeResolver' => $unionData['typeResolver'], + 'types' => $types, + 'description' => isset($unionData['description']) ? $unionData['description'] : '' + ] + ); + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php new file mode 100644 index 0000000000000..2d557e6dc5b84 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionInterface.php @@ -0,0 +1,23 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +use Magento\Framework\GraphQl\Config\ConfigElementInterface; + +/** + * Defines the contract for the union configuration data type. + */ +interface UnionInterface extends ConfigElementInterface +{ + /** + * Get a list of fields that make up the possible return or input values of a type. + * + * @return Type[] + */ + public function getTypes(): array; +} diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php new file mode 100644 index 0000000000000..5b27d03360efc --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/UnionType.php @@ -0,0 +1,92 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Config\Element; + +/** + * Class representing 'union' GraphQL config element. + */ +class UnionType implements UnionInterface +{ + /** + * @var string + */ + private $name; + + /** + * @var string[] + */ + private $types; + + /** + * @var string + */ + private $typeResolver; + + /** + * @var string + */ + private $description; + + /** + * @param string $name + * @param string $typeResolver + * @param string[] $types + * @param string $description + */ + public function __construct( + string $name, + string $typeResolver, + array $types, + string $description + ) { + $this->name = $name; + $this->types = $types; + $this->typeResolver = $typeResolver; + $this->description = $description; + } + + /** + * Get the type name. + * + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * Get a list of fields that make up the possible return or input values of a type. + * + * @return string[] + */ + public function getTypes(): array + { + return $this->types; + } + + /** + * Return the name of the resolver class that determines the concrete type to display in the result. + * + * @return string + */ + public function getTypeResolver(): string + { + return $this->typeResolver; + } + + /** + * Get a human-readable description of the type. + * + * @return string + */ + public function getDescription(): string + { + return $this->description; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Filter.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Filter.php index 0e5feeba3bade..5ec80b1d4692c 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Filter.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Filter.php @@ -51,7 +51,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc */ public function applyArgument( SearchCriteriaInterface $searchCriteria, diff --git a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Sort.php b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Sort.php index 6ecb1896d685a..d312837e41686 100644 --- a/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Sort.php +++ b/lib/internal/Magento/Framework/GraphQl/Query/Resolver/Argument/SearchCriteria/ArgumentApplier/Sort.php @@ -33,7 +33,7 @@ public function __construct(SortOrderBuilder $sortOrderBuilder = null) } /** - * {@inheritdoc} + * @inheritDoc */ public function applyArgument( SearchCriteriaInterface $searchCriteria, diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php index ad9fb675a6d70..3456f3c039a64 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php @@ -10,6 +10,7 @@ use Magento\Framework\GraphQl\Config\Data\WrappedTypeProcessor; use Magento\Framework\GraphQl\Config\Element\Field; use Magento\Framework\GraphQl\Config\Element\TypeInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\Input\InputMapper; use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface; use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper; @@ -89,17 +90,20 @@ public function __construct( /** * @inheritdoc */ - public function format(TypeInterface $configElement, OutputTypeInterface $outputType): array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array { - $typeConfig = [ - 'fields' => function () use ($configElement, $outputType) { - $fieldsConfig = []; - foreach ($configElement->getFields() as $field) { - $fieldsConfig[$field->getName()] = $this->getFieldConfig($configElement, $outputType, $field); + $typeConfig = []; + if ($configElement instanceof TypeInterface) { + $typeConfig = [ + 'fields' => function () use ($configElement, $outputType) { + $fieldsConfig = []; + foreach ($configElement->getFields() as $field) { + $fieldsConfig[$field->getName()] = $this->getFieldConfig($configElement, $outputType, $field); + } + return $fieldsConfig; } - return $fieldsConfig; - } - ]; + ]; + } return $typeConfig; } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php index 659c3f604508d..761ba68e44521 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Interfaces.php @@ -8,7 +8,7 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter; use Magento\Framework\GraphQl\Config\Element\Type; -use Magento\Framework\GraphQl\Config\Element\TypeInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface; use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper; @@ -32,9 +32,9 @@ public function __construct(OutputMapper $outputMapper) } /** - * {@inheritDoc} + * @inheritDoc */ - public function format(TypeInterface $configElement, OutputTypeInterface $outputType) : array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { $config = []; if ($configElement instanceof Type && !empty($configElement->getInterfaces())) { diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php index 3a40e609eb952..553e8fe40efc2 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/ResolveType.php @@ -8,7 +8,8 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter; use Magento\Framework\GraphQl\Config\Element\InterfaceType; -use Magento\Framework\GraphQl\Config\Element\TypeInterface; +use Magento\Framework\GraphQl\Config\Element\UnionType; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface; use Magento\Framework\ObjectManagerInterface; @@ -32,12 +33,12 @@ public function __construct(ObjectManagerInterface $objectManager) } /** - * {@inheritDoc} + * @inheritDoc */ - public function format(TypeInterface $configElement, OutputTypeInterface $outputType) : array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType) : array { $config = []; - if ($configElement instanceof InterfaceType) { + if ($configElement instanceof InterfaceType || $configElement instanceof UnionType) { $typeResolver = $this->objectManager->create($configElement->getTypeResolver()); $config['resolveType'] = function ($value) use ($typeResolver) { return $typeResolver->resolveType($value); diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php new file mode 100644 index 0000000000000..75b6a58790a09 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Unions.php @@ -0,0 +1,50 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\Formatter; + +use Magento\Framework\GraphQl\Config\Element\UnionType; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; +use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; +use Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper\FormatterInterface; +use Magento\Framework\GraphQl\Schema\Type\Output\OutputMapper; + +/** + * Add unions implemented by type if configured. + */ +class Unions implements FormatterInterface +{ + /** + * @var OutputMapper + */ + private $outputMapper; + + /** + * @param OutputMapper $outputMapper + */ + public function __construct(OutputMapper $outputMapper) + { + $this->outputMapper = $outputMapper; + } + + /** + * @inheritDoc + */ + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array + { + $config = []; + if ($configElement instanceof UnionType && !empty($configElement->getTypes())) { + $unionTypes = []; + foreach ($configElement->getTypes() as $unionName) { + $unionTypes[$unionName] = $this->outputMapper->getOutputType($unionName); + } + $config['types'] = $unionTypes; + } + + return $config; + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php index 416b4122b7097..c36df11012017 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterComposite.php @@ -7,11 +7,11 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper; -use Magento\Framework\GraphQl\Config\Element\TypeInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; /** - * {@inheritdoc} + * @inheritDoc */ class FormatterComposite implements FormatterInterface { @@ -29,18 +29,19 @@ public function __construct(array $formatters) } /** - * {@inheritDoc} + * @inheritDoc */ - public function format(TypeInterface $configElement, OutputTypeInterface $outputType) : array + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array { - $config = [ + $defaultConfig = [ 'name' => $configElement->getName(), 'description' => $configElement->getDescription() ]; + $formattedConfig = []; foreach ($this->formatters as $formatter) { - $config = array_merge($config, $formatter->format($configElement, $outputType)); + $formattedConfig[] = $formatter->format($configElement, $outputType); } - return $config; + return array_merge($defaultConfig, ...$formattedConfig); } } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php index 7d40b743a6a06..a99237aa8843a 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/FormatterInterface.php @@ -7,7 +7,7 @@ namespace Magento\Framework\GraphQl\Schema\Type\Output\ElementMapper; -use Magento\Framework\GraphQl\Config\Element\TypeInterface as TypeElementInterface; +use Magento\Framework\GraphQl\Config\ConfigElementInterface; use Magento\Framework\GraphQl\Schema\Type\OutputTypeInterface; /** @@ -18,9 +18,9 @@ interface FormatterInterface /** * Convert GraphQL config element to the object compatible with GraphQL schema generator. * - * @param TypeElementInterface $configElement + * @param ConfigElementInterface $configElement * @param OutputTypeInterface $outputType * @return array */ - public function format(TypeElementInterface $configElement, OutputTypeInterface $outputType) : array; + public function format(ConfigElementInterface $configElement, OutputTypeInterface $outputType): array; } diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php index 046eeb5b1f93d..afe0d84de26f8 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputMapper.php @@ -13,7 +13,7 @@ use Magento\Framework\Phrase; /** - * Map type names to their output type/interface/enum classes. + * Map type names to their output type/interface/union/enum classes. */ class OutputMapper { @@ -38,7 +38,7 @@ public function __construct( * @return OutputTypeInterface * @throws GraphQlInputException */ - public function getOutputType($typeName) + public function getOutputType(string $typeName) { $outputType = $this->typeRegistry->get($typeName); diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php new file mode 100644 index 0000000000000..a63b7b7417527 --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/OutputUnionObject.php @@ -0,0 +1,28 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Schema\Type\Output; + +use Magento\Framework\GraphQl\Config\Element\UnionType as UnionElement; +use Magento\Framework\GraphQl\Schema\Type\UnionType; + +/** + * The 'union' type compatible with GraphQL schema generator. + */ +class OutputUnionObject extends UnionType +{ + /** + * @param ElementMapper $elementMapper + * @param UnionElement $configElement + */ + public function __construct( + ElementMapper $elementMapper, + UnionElement $configElement + ) { + parent::__construct($elementMapper->buildSchemaArray($configElement, $this)); + } +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/UnionType.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/UnionType.php new file mode 100644 index 0000000000000..a843c6c669acf --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/UnionType.php @@ -0,0 +1,16 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQl\Schema\Type; + +/** + * Wrapper for GraphQl UnionType + */ +class UnionType extends \GraphQL\Type\Definition\UnionType implements OutputTypeInterface +{ + +} diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/TypeFactory.php b/lib/internal/Magento/Framework/GraphQl/Schema/TypeFactory.php index 3b31653382e3a..84f8d809c958e 100644 --- a/lib/internal/Magento/Framework/GraphQl/Schema/TypeFactory.php +++ b/lib/internal/Magento/Framework/GraphQl/Schema/TypeFactory.php @@ -13,7 +13,7 @@ use Magento\Framework\GraphQl\Schema\Type\EnumType; use Magento\Framework\GraphQl\Schema\Type\ListOfType; use Magento\Framework\GraphQl\Schema\Type\NonNull; -use Magento\Framework\GraphQl\Schema\TypeInterface; +use Magento\Framework\GraphQl\Schema\Type\UnionType; /** * Factory for @see TypeInterface implementations @@ -42,6 +42,17 @@ public function createInterface(array $config) : InterfaceType return new InterfaceType($config); } + /** + * Create an union type + * + * @param array $config + * @return UnionType + */ + public function createUnion(array $config) : UnionType + { + return new UnionType($config); + } + /** * Create an input object type * diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php index 1e8b33f79854b..9845646e09fd5 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader.php @@ -11,6 +11,7 @@ use Magento\Framework\Config\FileResolverInterface; use Magento\Framework\Config\ReaderInterface; use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface as TypeReaderComposite; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader\InterfaceType; /** * Reads *.graphqls files from modules and combines the results as array to be used with a library to configure objects @@ -21,6 +22,7 @@ class GraphQlReader implements ReaderInterface public const GRAPHQL_SCHEMA_FILE = 'schema.graphqls'; + /** @deprecated */ public const GRAPHQL_INTERFACE = 'graphql_interface'; /** @@ -69,7 +71,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc * * @param string|null $scope * @return array @@ -178,7 +180,7 @@ private function parseTypes(string $graphQlSchemaContent) : array private function copyInterfaceFieldsToConcreteTypes(array $source): array { foreach ($source as $interface) { - if ($interface['type'] == 'graphql_interface') { + if ($interface['type'] ?? '' == InterfaceType::GRAPHQL_INTERFACE) { foreach ($source as $typeName => $type) { if (isset($type['implements']) && isset($type['implements'][$interface['name']]) @@ -253,7 +255,7 @@ private function convertInterfacesToAnnotations(string $graphQlSchemaContent): s private function addPlaceHolderInSchema(string $graphQlSchemaContent) :string { $placeholderField = self::GRAPHQL_PLACEHOLDER_FIELD_NAME; - $typesKindsPattern = '(type|interface|input)'; + $typesKindsPattern = '(type|interface|input|union)'; $enumKindsPattern = '(enum)'; $typeNamePattern = '([_A-Za-z][_0-9A-Za-z]+)'; $typeDefinitionPattern = '([^\{]*)(\{[\s\t\n\r^\}]*\})'; @@ -328,13 +330,14 @@ private static function getModuleNameForRelevantFile(string $file): string */ private function addModuleNameToTypes(array $source, string $filePath): array { - foreach ($source as $typeName => $type) { - if (!isset($type['module']) && ( - ($type['type'] === self::GRAPHQL_INTERFACE && isset($type['typeResolver'])) - || isset($type['implements']) - ) - ) { - $source[$typeName]['module'] = self::getModuleNameForRelevantFile($filePath); + foreach ($source as $typeName => $typeDefinition) { + if (!isset($typeDefinition['module'])) { + $hasTypeResolver = (bool)($typeDefinition['typeResolver'] ?? false); + $hasImplements = (bool)($typeDefinition['implements'] ?? false); + $typeDefinition = (bool)($typeDefinition['type'] ?? false); + if ((($typeDefinition === InterfaceType::GRAPHQL_INTERFACE && $hasTypeResolver) || $hasImplements)) { + $source[$typeName]['module'] = self::getModuleNameForRelevantFile($filePath); + } } } diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php index e4dec7afdab0a..c6e0481c7490e 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php @@ -15,6 +15,8 @@ */ class EnumType implements TypeMetaReaderInterface { + public const GRAPHQL_ENUM = 'graphql_enum'; + /** * @var DocReader */ @@ -30,14 +32,14 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { if ($typeMeta instanceof \GraphQL\Type\Definition\EnumType) { $result = [ 'name' => $typeMeta->name, - 'type' => 'graphql_enum', + 'type' => self::GRAPHQL_ENUM, 'items' => [] // Populated later ]; foreach ($typeMeta->getValues() as $enumValueMeta) { diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php index 38159fac03b3b..2108a04d8a9ed 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InputObjectType.php @@ -17,6 +17,8 @@ */ class InputObjectType implements TypeMetaReaderInterface { + public const GRAPHQL_INPUT = 'graphql_input'; + /** * @var TypeMetaWrapperReader */ @@ -49,7 +51,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { @@ -57,7 +59,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $typeName = $typeMeta->name; $result = [ 'name' => $typeName, - 'type' => 'graphql_input', + 'type' => self::GRAPHQL_INPUT, 'fields' => [] // Populated later ]; $fields = $typeMeta->getFields(); diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php index baadb4be61cf2..76550469d409e 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/InterfaceType.php @@ -17,6 +17,8 @@ */ class InterfaceType implements TypeMetaReaderInterface { + public const GRAPHQL_INTERFACE = 'graphql_interface'; + /** * @var FieldMetaReader */ @@ -49,7 +51,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { @@ -57,7 +59,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $typeName = $typeMeta->name; $result = [ 'name' => $typeName, - 'type' => 'graphql_interface', + 'type' => self::GRAPHQL_INTERFACE, 'fields' => [] ]; diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php index ba8e46dd60557..3ad6d69eb5c9c 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php @@ -19,6 +19,8 @@ */ class ObjectType implements TypeMetaReaderInterface { + public const GRAPHQL_TYPE = 'graphql_type'; + /** * @var FieldMetaReader */ @@ -69,7 +71,7 @@ public function __construct( } /** - * @inheritdoc + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { @@ -77,7 +79,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array $typeName = $typeMeta->name; $result = [ 'name' => $typeName, - 'type' => 'graphql_type', + 'type' => self::GRAPHQL_TYPE, 'fields' => [], // Populated later ]; diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php new file mode 100644 index 0000000000000..5776901765b5a --- /dev/null +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/UnionType.php @@ -0,0 +1,106 @@ +<?php +/** + * Copyright © Magento, Inc. All rights reserved. + * See COPYING.txt for license details. + */ +declare(strict_types=1); + +namespace Magento\Framework\GraphQlSchemaStitching\GraphQlReader\Reader; + +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\TypeMetaReaderInterface; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\FieldMetaReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader; +use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\CacheAnnotationReader; + +/** + * Composite configuration reader to handle the union object type meta + */ +class UnionType implements TypeMetaReaderInterface +{ + public const GRAPHQL_UNION = 'graphql_union'; + + /** + * @var FieldMetaReader + */ + private $fieldMetaReader; + + /** + * @var DocReader + */ + private $docReader; + + /** + * @var CacheAnnotationReader + */ + private $cacheAnnotationReader; + + /** + * @param FieldMetaReader $fieldMetaReader + * @param DocReader $docReader + * @param CacheAnnotationReader|null $cacheAnnotationReader + */ + public function __construct( + FieldMetaReader $fieldMetaReader, + DocReader $docReader, + CacheAnnotationReader $cacheAnnotationReader = null + ) { + $this->fieldMetaReader = $fieldMetaReader; + $this->docReader = $docReader; + $this->cacheAnnotationReader = $cacheAnnotationReader ?? \Magento\Framework\App\ObjectManager::getInstance() + ->get(CacheAnnotationReader::class); + } + + /** + * @inheritDoc + */ + public function read(\GraphQL\Type\Definition\Type $typeMeta): array + { + if ($typeMeta instanceof \GraphQL\Type\Definition\UnionType) { + $typeName = $typeMeta->name; + $result = [ + 'name' => $typeName, + 'type' => self::GRAPHQL_UNION, + 'types' => [], + ]; + + $unionTypeResolver = $this->getUnionTypeResolver($typeMeta); + if (!empty($unionTypeResolver)) { + $result['typeResolver'] = $unionTypeResolver; + } + + foreach ($typeMeta->getTypes() as $type) { + $result['types'][] = $type->name; + } + + if ($this->docReader->read($typeMeta->astNode->directives)) { + $result['description'] = $this->docReader->read($typeMeta->astNode->directives); + } + + return $result; + } else { + return []; + } + } + + /** + * Retrieve the union type resolver if it exists from the meta data + * + * @param \GraphQL\Type\Definition\UnionType $unionTypeMeta + * @return string + */ + private function getUnionTypeResolver(\GraphQL\Type\Definition\UnionType $unionTypeMeta): string + { + /** @var \GraphQL\Language\AST\NodeList $directives */ + $directives = $unionTypeMeta->astNode->directives; + foreach ($directives as $directive) { + if ($directive->name->value == 'typeResolver') { + foreach ($directive->arguments as $directiveArgument) { + if ($directiveArgument->name->value == 'class') { + return $directiveArgument->value->value; + } + } + } + } + return ''; + } +} diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/TypeReaderComposite.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/TypeReaderComposite.php index e9b899bb2bb5b..614c1e3a743a0 100644 --- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/TypeReaderComposite.php +++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/TypeReaderComposite.php @@ -25,7 +25,7 @@ public function __construct( } /** - * {@inheritdoc} + * @inheritDoc */ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array { diff --git a/lib/internal/Magento/Framework/Registry.php b/lib/internal/Magento/Framework/Registry.php index e798b28e1e1b2..b5944729fd1a1 100644 --- a/lib/internal/Magento/Framework/Registry.php +++ b/lib/internal/Magento/Framework/Registry.php @@ -9,7 +9,7 @@ * Registry model. Used to manage values in registry * * Registry usage as a shared service introduces temporal, hard to detect coupling into system. - * It's usage should be avoid. Use service classes or data providers instead. + * Its usage should be avoided. Use service classes or data providers instead. * * @api * @deprecated 102.0.0 diff --git a/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php b/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php index 0791c89ab793a..42ffeae2aa883 100644 --- a/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php +++ b/lib/internal/Magento/Framework/Stdlib/DateTime/Timezone.php @@ -319,7 +319,7 @@ public function convertConfigTimeToUtc($date, $format = 'Y-m-d H:i:s') throw new LocalizedException( new Phrase( 'The DateTime object timezone needs to be the same as the "%1" timezone in config.', - $this->getConfigTimezone() + [$this->getConfigTimezone()] ) ); } diff --git a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php index b72995bb39855..09f85567e4c0d 100644 --- a/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php +++ b/lib/internal/Magento/Framework/Stdlib/Test/Unit/DateTime/TimezoneTest.php @@ -9,6 +9,7 @@ use Magento\Framework\App\Config\ScopeConfigInterface; use Magento\Framework\App\ScopeResolverInterface; +use Magento\Framework\Exception\LocalizedException; use Magento\Framework\Locale\ResolverInterface; use Magento\Framework\Stdlib\DateTime; use Magento\Framework\Stdlib\DateTime\Timezone; @@ -208,6 +209,30 @@ public function testDate($expectedResult, $timezone, $date) ); } + /** + * Data provider for testException + * + * @return array + */ + public function getConvertConfigTimeToUTCDataFixtures() + { + return [ + 'datetime' => [ + new \DateTime('2016-10-10 10:00:00', new \DateTimeZone('UTC')) + ] + ]; + } + + /** + * @dataProvider getConvertConfigTimeToUTCDataFixtures + */ + public function testConvertConfigTimeToUtcException($date) + { + $this->expectException(LocalizedException::class); + + $this->getTimezone()->convertConfigTimeToUtc($date); + } + /** * DataProvider for testDate * diff --git a/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php b/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php index 1b12d68e683a3..67d5e303c6896 100644 --- a/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php +++ b/lib/internal/Magento/Framework/Test/Unit/App/ResourceConnectionTest.php @@ -84,7 +84,7 @@ public function testGetTablePrefixWithInjectedPrefix() public function testGetTablePrefix() { - $this->deploymentConfigMock->expects(self::once()) + $this->deploymentConfigMock->expects($this->once()) ->method('get') ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_PREFIX) ->willReturn('pref_'); @@ -93,10 +93,10 @@ public function testGetTablePrefix() public function testGetConnectionByName() { - $this->deploymentConfigMock->expects(self::once())->method('get') + $this->deploymentConfigMock->expects($this->once())->method('get') ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_CONNECTIONS . '/default') ->willReturn(['config']); - $this->connectionFactoryMock->expects(self::once())->method('create') + $this->connectionFactoryMock->expects($this->once())->method('create') ->with(['config']) ->willReturn('connection'); @@ -112,15 +112,38 @@ public function testGetExistingConnectionByName() 'connections' => ['default_process_' . getmypid() => 'existing_connection'] ] ); - $this->deploymentConfigMock->expects(self::never())->method('get'); + $this->deploymentConfigMock->expects($this->never())->method('get'); self::assertEquals('existing_connection', $unit->getConnectionByName('default')); } public function testCloseConnection() { - $this->configMock->expects(self::once())->method('getConnectionName')->with('default'); + $this->configMock->expects($this->once())->method('getConnectionName')->with('default'); $this->unit->closeConnection('default'); } + + public function testGetTableNameWithBoolParam() + { + $this->deploymentConfigMock->expects($this->at(0)) + ->method('get') + ->with(ConfigOptionsListConstants::CONFIG_PATH_DB_PREFIX) + ->willReturn('pref_'); + $this->deploymentConfigMock->expects($this->at(1))->method('get') + ->with('db/connection/default') + ->willReturn(['config']); + $this->configMock->expects($this->atLeastOnce()) + ->method('getConnectionName') + ->with('default') + ->willReturn('default'); + + $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)->getMock(); + $connection->expects($this->once())->method('getTableName')->with('pref_1'); + $this->connectionFactoryMock->expects($this->once())->method('create') + ->with(['config']) + ->willReturn($connection); + + $this->unit->getTableName(true); + } } diff --git a/lib/internal/Magento/Framework/View/Layout.php b/lib/internal/Magento/Framework/View/Layout.php index ebefc6200cdfa..ce8b086dc7b84 100644 --- a/lib/internal/Magento/Framework/View/Layout.php +++ b/lib/internal/Magento/Framework/View/Layout.php @@ -547,8 +547,7 @@ public function renderNonCachedElement($name) if ($this->appState->getMode() === AppState::MODE_DEVELOPER) { throw $e; } - $message = ($e instanceof LocalizedException) ? $e->getLogMessage() : $e->getMessage(); - $this->logger->critical($message); + $this->logger->critical($e); } return $result; } diff --git a/lib/internal/Magento/Framework/View/Template/Html/Minifier.php b/lib/internal/Magento/Framework/View/Template/Html/Minifier.php index 0a8db80cae349..cdbce9d102a89 100644 --- a/lib/internal/Magento/Framework/View/Template/Html/Minifier.php +++ b/lib/internal/Magento/Framework/View/Template/Html/Minifier.php @@ -3,6 +3,7 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); namespace Magento\Framework\View\Template\Html; @@ -140,7 +141,7 @@ function ($match) use (&$heredocs) { . '(?:<(?>textarea|pre|script)\b|\z))#', ' ', preg_replace( - '#(?<!:|\\\\|\'|")//(?!\s*\<\!\[)(?!\s*]]\>)[^\n\r]*#', + '#(?<!:|\\\\|\'|"|/)//(?!/)(?!\s*\<\!\[)(?!\s*]]\>)[^\n\r]*#', '', preg_replace( '#(?<!:|\'|")//[^\n\r]*(\?\>)#', diff --git a/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php b/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php index 23f35dfab7cc8..31606b55f6519 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/LayoutTest.php @@ -19,6 +19,7 @@ use Magento\Framework\View\Element\AbstractBlock; use Magento\Framework\View\Element\Template; use Magento\Framework\View\Layout; +use Magento\Framework\View\Layout\BuilderInterface; use Magento\Framework\View\Layout\Data\Structure as LayoutStructure; use Magento\Framework\View\Layout\Element; use Magento\Framework\View\Layout\Generator\Block; @@ -1169,4 +1170,27 @@ public function renderElementDisplayDataProvider(): array [null], ]; } + + /** + * Test render element with exception + * + * @return void + */ + public function testRenderNonCachedElementWithException(): void + { + $exception = new \Exception('Error message'); + + $builderMock = $this->createMock(BuilderInterface::class); + $builderMock->expects($this->once()) + ->method('build') + ->willThrowException($exception); + + $this->loggerMock->expects($this->once()) + ->method('critical') + ->with($exception); + + $model = clone $this->model; + $model->setBuilder($builderMock); + $model->renderNonCachedElement('test_container'); + } } diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php index 6aafa5a46cf63..3b13a2f723617 100644 --- a/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php +++ b/lib/internal/Magento/Framework/View/Test/Unit/Template/Html/MinifierTest.php @@ -3,6 +3,8 @@ * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ +declare(strict_types=1); + namespace Magento\Framework\View\Test\Unit\Template\Html; use PHPUnit\Framework\TestCase; @@ -139,6 +141,7 @@ public function testMinify() <img src="test.png" alt="some text" /> <?php echo \$block->someMethod(); ?> <div style="width: 800px" class="<?php echo \$block->getClass() ?>" /> + <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" data-component="main-image"> <script> var i = 1;// comment var j = 1;// <?php echo 'hi' ?> @@ -179,7 +182,7 @@ public function testMinify() TEXT; $expectedContent = <<<TEXT -<?php /** * Copyright © Magento, Inc. All rights reserved. * See COPYING.txt for license details. */ ?> <?php ?> <html><head><title>Test titleText Link some textsomeMethod(); ?>